1

I saw a lot of questions over the internet on how to determine the number of token holders in solidity and everybody says that its impossible to be determined without interacting with a solution that is stored off the blockchain.

I have an idea for this issue that got stuck in my head and I cannot see any downside except slightly higher gas required for each transaction.

The solution would be to define a unit property in the contract and initialize it in the constructor with the number of addresses that will hold balance at the end of the constructor logic execution. In the method that performs the transaction, write some logic that will increase the counter if the balance of the receiver is 0 and decrease the counter if the balance of the sender becomes 0 after the transaction.

  contract MyToken is ERC20{
    uint holders;
    constructor(string memory name, string memory symbol) ERC20 (name, symbol){
        holders = 0;
    } 
    
    function transfer(address recipient, uint256 amount) public override returns (bool) {    
    
        _transfer(_msgSender(), recipient, amount);
        if(balanceOf(recipient) - amount == 0) holders++;
        if(balanceOf(_msgSender()) == 0) holders--;
        
        return true;
    
    }
 }

Is there anything that I miss and will cause this solution to fail?

2
  • 1
    That should work, similar modifications has to be made for every function that changes the balances, for example transferFrom, burn or mint.
    – Ismael
    May 16, 2021 at 4:00
  • This costs more gas but it is not really that much useful, proper info is given when you see how much each holders holds, this is more in statistics department and it is good it is not part of SC.
    – Blissful
    Apr 20, 2022 at 21:59

2 Answers 2

1

I wonder why OpenZappelin didn't implement so useful functionality. Talking about your code:

  1. holders = 0; not necessary, it is already zero
  2. you don't count partially sending, it should increase holders count
  3. compute changes for all operations transfer/transferFrom/mint/burn or use OpenZappelin ERC20 implementation and use_beforeTokenTransfer (or reproduce it in your ERC20 class)
1
  • What do you mean by partially sending? Jul 21, 2021 at 7:42
0

I use the following "updateHolder" modifier in order to check any functions that mint or burn tokens, with minimal complexity to keep gas consumption as low as possible while still being easy to maintain and read. I'm aware that this could be overridden in the transfer function but I believe this is better imo.

contract MyToken is ERC20 {
    uint holders;
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        holders = 0;
    }

    modifier updateHolders(address sender, address recipient, uint256 amount) {
        // Store previous balances
        uint256 prevSenderBalance = balanceOf(sender);
        uint256 prevRecipientBalance = balanceOf(recipient);

        _;

        // Check if the sender's balance has decreased to zero
        if (prevSenderBalance > 0 && balanceOf(sender) == 0) {
            holders--;
        }
        // Check if the recipient's balance has increased from zero
        if (prevRecipientBalance == 0 && balanceOf(recipient) > 0) {
            holders++;
        }
    }

    function transfer(address recipient, uint256 amount)
        public
        override
        updateHolders(_msgSender(), recipient, amount)
        returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    // Add the updateHolders modifier to other functions affecting token balances, like mint, burn, etc.
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.