Write your own simple dividend ERC20 Token
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 receives10 ether
and the total supply of tokens is10 divs
, therefore the quantitydividendPerToken
equals to 1. - xDividendPerToken: is the amount per
div
already withdrawn by holder of thosediv
tokens. Based on the last example, one of the holders Alice, owning5 divs
, withdraws her share10/2 = 5 divs
. The quantityxDividendPerToken
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.
Thank you! You are a life saver!
Please let me know if you are interested in working with me on this tutorial.
https://apkfl.com/
I would like to know more about this approach for my tech startup. Can you email me how to schedule a meeting with you?
Feel free to drop me an email me about that 📧
Replied at hello@zokyo.io.