Welcome back. In our previous lesson, we established the design for our security token's dividend distribution mechanism. We chose a "pull-over-push" pattern to ensure efficiency and security, and outlined a pro-rata accounting model based on token ownership.
Today, we transition from design to implementation. This lesson will guide you through writing the Solidity code for this mechanism within our SecurityToken contract. We will implement the depositDividends and withdrawDividends functions and, most critically, secure the withdrawal process against reentrancy attacks using OpenZeppelin's ReentrancyGuard. This will put into practice the security principles we discussed and give you hands-on experience with a crucial pattern for any contract that handles value distribution.
1. State Variables for Dividend Accounting
Before we can write the functions, we need to add the necessary state variables to our SecurityToken contract to track dividends. Our model, similar to patterns used in production systems, won't track every individual's share directly. Instead, it uses an accumulator to track the total dividends distributed per token. This is a highly efficient accounting method for a distributed environment.
We will add the following state variables:
// The ERC20 token used for paying dividends (e.g., USDC)
IERC20 public dividendToken;
// An accumulator for dividends paid out per token, scaled for precision
uint256 public dividendsPerToken;
// Tracks the last `dividendsPerToken` value at which a user claimed their share
mapping(address => uint256) public dividendsClaimedPerToken;
dividendToken: This will store the address of the ERC-20 contract used for dividend payments. We'll set this in the constructor.dividendsPerToken: This is the core of our accounting. It accumulates the value of dividends distributed for each single token in circulation. We scale this value to maintain precision, as Solidity does not support floating-point numbers.dividendsClaimedPerToken: This mapping records, for each user, the value ofdividendsPerTokenat the time of their last withdrawal. This prevents double-claiming. A user's pending dividend is proportional to the difference between the current globaldividendsPerTokenand their personaldividendsClaimedPerTokenvalue.
The article below provides an excellent walkthrough of a similar differential dividend model. Pay close attention to its explanation of dividendPerToken.
Write your own simple dividend ERC20 Token - Codementor
This article from Codementor explains a common pattern for on-chain dividend distribution that we will be adapting.
In the "Approach" section, read the explanation of the two variables, dividendPerToken and xDividendPerToken. Our dividendsClaimedPerToken serves the same purpose as their xDividendPerToken. In the "Deposit" section, review the logic for how the contract updates dividendPerToken when new funds arrive. We will implement a similar logic for ERC-20 tokens.
2. Implementing depositDividends
The depositDividends function is the administrative entry point for funding the dividend pool. It will be restricted to an address holding a specific role, which we'll call DIVIDEND_ADMIN_ROLE.
The function's logic is as follows:
- It accepts one argument: the
amountof dividend tokens to be deposited. - It transfers this
amountfrom the administrator's wallet to theSecurityTokencontract. This requires the administrator to first callapprove()on the dividend token contract, granting ourSecurityTokencontract a spending allowance. - It updates the
dividendsPerTokenaccumulator.
The calculation to update the accumulator is crucial:
dividendsPerToken += (amount * 1e18) / totalSupply();
We multiply amount by 1e18 (a common practice for precision) before dividing by totalSupply() to avoid losing precision due to integer division.
Here is the implementation. You would add this function to your SecurityToken.sol file.
// Define a role for the dividend administrator
bytes32 public constant DIVIDEND_ADMIN_ROLE = keccak256("DIVIDEND_ADMIN_ROLE");
// ... inside your contract ...
function depositDividends(uint256 amount)
external
onlyRole(DIVIDEND_ADMIN_ROLE)
{
require(totalSupply() > 0, "Cannot deposit dividends with zero total supply");
// Transfer dividend tokens from the admin to this contract
// The admin must have approved this contract to spend 'amount'
dividendToken.transferFrom(msg.sender, address(this), amount);
// Update the accumulator
dividendsPerToken += (amount * 1e18) / totalSupply();
emit DividendsDeposited(msg.sender, amount);
}
(Note: You would also need to define the DividendsDeposited event: event DividendsDeposited(address indexed depositor, uint256 amount);)
3. Implementing withdrawDividends and Preventing Reentrancy
This function allows any token holder to pull their rightful share of the dividends from the contract. Its implementation must be precise and, above all, secure.
The Reentrancy Vulnerability
The withdrawDividends function will make an external call when it transfers the dividend tokens to the user. This creates a potential attack vector known as a reentrancy attack. If a malicious user calls this function from another smart contract, they could craft their contract to immediately call withdrawDividends again after receiving the funds but before our contract has finished its execution and updated its state.
This recursive call could allow the attacker to drain the contract's entire dividend balance. The diagram below illustrates this cyclical, malicious call flow.

To understand this vulnerability in-depth, watch the following video from Dapp University. It clearly explains the attack and introduces the standard solution.
How to Hack Smart Contracts With Reentrancy
This video provides a clear, practical demonstration of a reentrancy attack and how to prevent it.
First, watch the explanation of what a reentrancy attack is at a high level. Next, see how the vulnerable withdraw function is coded in the Bank contract example. Notice the order of operations: it sends Ether before updating the user's balance. This is the flaw. Finally, focus on the solution using OpenZeppelin's ReentrancyGuard. This section explains how to import the guard, inherit from it, and apply the nonReentrant modifier. This is exactly what we will do.
The Secure Implementation
To mitigate this, we will use two layers of defense:
- The Checks-Effects-Interactions (CEI) pattern: First check conditions, then update state (effects), and only then interact with external contracts.
- OpenZeppelin's
ReentrancyGuard: A robust, battle-tested modifier that provides a lock, preventing any re-entrant calls to functions that use it.
First, let's look at the implementation with the CEI pattern:
function withdrawDividends() external {
address user = msg.sender;
uint256 userBalance = balanceOf(user);
require(userBalance > 0, "User has no tokens");
// Calculate pending dividends
uint256 pendingDividends = (userBalance * (dividendsPerToken - dividendsClaimedPerToken[user])) / 1e18;
// --- CHECKS ---
require(pendingDividends > 0, "No dividends to withdraw");
// --- EFFECTS ---
// Mark dividends as claimed BEFORE the transfer
dividendsClaimedPerToken[user] = dividendsPerToken;
// --- INTERACTIONS ---
// Transfer the funds
dividendToken.transfer(user, pendingDividends);
emit DividendsWithdrawn(user, pendingDividends);
}
Now, let's add the final layer of protection. To use ReentrancyGuard, you'll first import it at the top of your SecurityToken.sol file and make your contract inherit from it:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecurityToken is ERC20, AccessControl, ReentrancyGuard {
// ...
}
Then, you simply add the nonReentrant modifier to the withdrawDividends function:
function withdrawDividends() external nonReentrant {
// ... function body remains the same
}
This simple addition ensures that while the withdrawDividends function is executing, no other function with the nonReentrant modifier can be called until the first call has completed. It's a powerful and standard safeguard.
The following resources provide more context on the pull pattern and the security measures we've just implemented.
How to Automate Dividend & Interest Payouts with Smart Contracts
This article reinforces the architectural and security patterns we've discussed.
Under "Prerequisites and System Architecture," read the paragraph describing the pull-over-push pattern. In the same section, review the paragraph on security, which explicitly mentions the Checks-Effects-Interactions pattern. Finally, under "Frequently Asked Questions," the article provides a concise comparison of push vs. pull models, confirming why our chosen approach is standard practice.
Conclusion
In this lesson, you have successfully translated a high-level design into working, secure Solidity code. You've implemented a complete dividend distribution mechanism using an efficient, differential accounting model.
Key Takeaways:
- A dividend system can be efficiently implemented using an accumulator (
dividendsPerToken) and a mapping to track individual claims (dividendsClaimedPerToken). - The
depositDividendsfunction serves as a permissioned entry point to fund the dividend pool, updating the central accumulator. - The
withdrawDividendsfunction allows users to pull their funds, following the secure and gas-efficient pull pattern. - Security against reentrancy is non-negotiable. We defended our contract by adhering to the Checks-Effects-Interactions pattern and implementing the industry-standard
ReentrancyGuardfrom OpenZeppelin.
You now have a security token with core administrative features, compliance controls, and a value distribution mechanism. In the next module, we will write a comprehensive suite of tests to verify that every piece of this complex system—including the dividend logic and its security protections—works exactly as intended.