Codementor Events

Write your own simple dividend ERC20 Token

Published Mar 14, 2022Last updated Sep 09, 2022

Tokens provide value for their holders. This stemmed from the capability of smart contracts to give the rights to ownership of assets on the blockchain in a trustless way. One scenario is to have tokens represent an underlying asset to be shared proportionally according to the balance of those tokens which a holder (i.e. investor) is holding. This article shows how to write an ERC20 token smart contract in solidity that divides an underlying asset (e.g. ether) among the holders in the form of dividends, let's call it the div token.

Approach

A straightforward approach is to have the asset divided and recorded to each one of the holders of the token. This takes place by instantly dividing the amount of asset received by the total supply of the token then multiplying that by the amount of dividends each holder is having which is represented by her token balance. As much as this approach might sound logical and the goto solution for our usecase, it is actually nonsensical in the world of solidity smart contracts in which we face the following two complications:

  • Lack of support for floating point operations in the language itself.
  • Developers account for computation since it costs funds to execute on the blockchain.

The first point states that this limitation will lead to lost funds due to dividing non decomposable integer values. On the other hand the second one, which is the main reason the approach suggested in this article is used, forces developers to find the approach that avoids expensive unnecessary computation.

In the light of this, I write the code to track only the amount to be received per token dividendPerToken and a mapping to track the dividend per token amount already withdrawn by an investor xDividendPerToken.

uint256 dividendPerToken;
mapping (address => uint256) xDividendPerToken;
  • dividendPerToken: is the amount of the underlying asset (e.g. ether) per token. This amount is updated when the contract receives ether (i.e. underlying asset of our divs). For instance, suppose the contract receives 10 ether and the total supply of tokens is 10 divs, therefore the quantity dividendPerToken equals to 1.
  • xDividendPerToken: is the amount per div already withdrawn by holder of those div tokens. Based on the last example, one of the holders Alice, owning 5 divs, withdraws her share 10/2 = 5 divs. The quantity xDividendPerToken for Alice equals to 1 in that case.

Deposit

The underlying asset of the contract to be shared amongst the holders is ether. This underlying asset is representing the native coin of the chain (i.e. ether in Ethereum Mainnet, matic in Polygon Mainnet and so on, ...). This makes it special in solidity terms since sending native coin to a contract's address triggers the receive() function of that contract which is a special function with logic implemented when we need to do something on receiving this native coin.

receive() external payable {
    require(totalSupply() != 0, "No tokens minted");
    dividendPerToken += msg.value / totalSupply();    
    emit FundsReceived(msg.value, dividendPerToken);
}

it is no secret that what we want to do is to update the value of dividendPerToken on receiving new ether. First line of the implementation, we assure that we have a non-zero supply of divs that is the total amount of tokens minted, so that we do not end up dividing by zero in case nobody has minted any divs yet which means that there are no holders to distribute the asset amongst anyway. Then the amount of ether received which is stored in msg.value is divided by the current total supply of the divs and accumulated on the last value of dividendPerToken. Lastly, the contract emits an event for the world outside the blockchain to know that the contract received some amount of asset and dividendPerToken had its value updated.

It is worth noting that dividendPerToken multiplied by the balance of holder returns the amount to be withdrawn by the holder given that xDividendPerToken is zero. For a non-zero xDividendPerToken the withdrawal equation is

withdrawal = balance(holder) * (dividendPerToken - xDividendPerToken(holder))

Mint

Minting divs should be as easy as writing _mint(to_, amount_) where to_ is the address of the recipient of minted divs and amount_ is the quantity of tokens to be minted to the recipient.
But minting new tokens updates balance(holder) which affects the equation of withdrawal, therefore on minting new tokens, funds should be withdrawn to the recipient before that. I introduce a new mapping variable credit for that purpose in order to store the amount of underlying assets which to be withdrawn to the recipient rather than actually withdrawing the assets.

mapping (address => uint256) credit;

This function is implemented prior to minting new divs for that purpose

function _withdrawToCredit(
    address to_
) private
{
    uint256 recipientBalance = balanceOf(to_);
    uint256 amount = (dividendPerToken - xDividendPerToken[to_]) * recipientBalance ;
    credit[to_] += amount;
    xDividendPerToken[to_] = dividendPerToken;
}

Starting from the second line of the implementation of _withdrawToCredit, the values are plugged onto the withdrawal equation in order to get the amount to be withdrawn to the holder to_. Rather than sending the ether to the holder rightaway, it is stored into the credit mapping of that holder. Lastly, update the xDividendPerToken of that holder to indicate that she withdrew that portion of the dividend.

Finally, here is the minting logic

function mint(address to_, uint256 amount_) public onlyOwner {
    _withdrawToCredit(to_);
    _mint(to_, amount_);
}

Withdraw

Here comes the interesting part which is about withdrawing the underlying assets for the holder of our div token.

function withdraw() external {
    uint256 holderBalance = balanceOf(_msgSender());
    require(holderBalance != 0, "DToken: caller possess no shares");

    uint256 amount = ( (dividendPerToken - xDividendPerToken[_msgSender()]) * holderBalance );
    amount += credit[_msgSender()];
    credit[_msgSender()] = 0;
    xDividendPerToken[_msgSender()] = dividendPerToken;

    (bool success, ) = payable(_msgSender()).call{value: amount}("");
    require(success, "DToken: Could not withdraw eth");
}

The amount to be received is proportional to the balance of the holder according to the withdrawal equation referred to earlier in this article. This amount is added to the current credit the holder is having due to minting or transfers that took place. Then the values of credit and xDividendPerToken are updated for that holder. Finally the contract sends the amount of the underlying asset to the holder who called this withdraw function.

Transfer

Transferring an amount of divs from one holder to another is a part of ERC20 standard contract which this contract inherits. Therefore nothing much needed to be done in this part except for noticing the fact that transferring token amount between two holders changes the balances of those holders therefore we need to account for that before the withdrawal of the underlying asset by those two holders takes place. ERC20 standard provides us with a hook function to implement this before transferring tokens: _beforeTokenTransfer(address from,address to,uint256 amount).

function _beforeTokenTransfer(
    address from,
    address to,
    uint256 amount
) internal override {
    if(from == address (0) || to == address(0)) return;
    _withdrawToCredit(to);
    _withdrawToCredit(from);
}

from and to are the addresses of sender and recipient respectively. Those addresses refer to the holders involved in the transfer, hence their credit is updated accordingly before the transfer is executed. A zero address for sender implies minting while for recipient, the implication is that the token is being burnt, in those two cases the function returns instantly without executing the withdrawals.

Conclusion

We leverage the ERC20 standard to build a contract that distributes dividends among holders proportional to their balances. Also note that a good smart contract is never good without thorough testing that reproduces scenarios in which holders act in a way similar to the real world. Refer to this repo on github which includes the smart contract and the tests.

Discover and read more posts from beber89
get started
post commentsBe the first to share your opinion
Einar César
a year ago

Thank you! You are a life saver!

Apkwineoi Apkwineoi
2 years ago

Please let me know if you are interested in working with me on this tutorial.
https://apkfl.com/

douglas dimick
2 years ago

I would like to know more about this approach for my tech startup. Can you email me how to schedule a meeting with you?

beber89
2 years ago

Feel free to drop me an email me about that 📧

douglas dimick
2 years ago

Replied at hello@zokyo.io.

Show more replies