10

I tried to write an airdrop contract which takes a list of recipients and a list of balances and then can be commanded to airdrop all at once. With code below I can launch a single transaction able to drop tokens to 100 addresses at once. It cost around 3.5M gas, which is huge and close to the maximum per transaction. Launching a single transfer with my particular token costs around 58k gas. So the batch is more efficient using only 60% of the gas needed by multiple transactions yet I'm bit disappointed because I expected a much bigger saving. I'm looking for a better strategy if any.

pragma solidity ^0.4.17;


import "zeppelin-solidity/contracts/token/ERC20Basic.sol";


contract Airdrop{
    address[] public recipients;
    uint256[] public balances;
    ERC20Basic public token;
  address owner;


    function Airdrop(address _token) public{
        require(_token != address(0));
        token = ERC20Basic(_token);
    owner = msg.sender;
    }

    function setRecipientsAndBalances(address[] _recipients, uint256[] _balances) public {
        require(_recipients.length == _balances.length);
    require(msg.sender == owner);
        recipients = _recipients;
        balances = _balances;
    }

    function doAirdrop() public returns(uint){
      require(msg.sender == owner);
        require(recipients.length>0);
        for(uint i=0; i < recipients.length; i++){
            if(token.balanceOf(this)>=balances[i]){
                token.transfer(recipients[i],balances[i]);
            }
            else{
                return i;
            }
        }
        return recipients.length;
    }

}

Then I tried to launch with a script like this (code is not complete)

  it('should do massive airdrop to cause gas issues', async function () {

    //an empty array
    var a = [];

    //generate 100 integers and push in a
    for (i=100;i<200;i++) a.push(i);

    //generate 100 0x addresses appending the integers
    const addr = a.map(x => "0x0000000000000000000000000000000000000" + x );

    console.log(addr);

    //generate an array of balances all equal to 1000
    let balances = addr.map(x => 1000);
    console.log(balances);    

    //call the airdrop contract to set the recipients and balances
    await this.airdrop.setRecipientsAndBalances(addr,balances).should.be.fulfilled;

    //call the airdrop to finally airdrop
    let result = await this.airdrop.doAirdrop();

    //write gas on the console
    console.log("gasUsed:"+result.receipt.gasUsed);

  });

I get a gas usage of 3.5ml gas

4 Answers 4

6
  • You are saving the addresses and balances in the contracts storage, that is expensive. You can send them as parameters and never store them.

  • You are checking if you have enough funds before each transfer that should not be necesary, transfer internally should checks for the sender balance. Remove checks as redundant.

Something like this should work better

function doAirdrop(address[] _recipients, uint256[] _balances) public {
    require(msg.sender == owner);
    require(_recipients.length == _balances.length);
    for (uint i=0; i < _recipients.length; i++) {
        token.transfer(_recipients[i], _balances[i]);
    }
}
1
5

In addition to the answer by Ismael showing the default way to airdrop tokens, you might do that even more efficiently by implementing an airdrop function in the token itself. The architecture is less pretty but more gas efficient:

uint256 public tokenAmountToAirdrop; 

function doAirdrop(address[] _recipients, uint256[] _balances) public {
    require(msg.sender == owner);
    for (uint256 i=0; i < _recipients.length; i++) {
        require(tokenAmountToAirdrop >= _balances[i]);
        tokenAmountToAirdrop = tokenAmountToAirdrop - _balances[i];
        balances[_recipients[i]] = balances[_recipients[i]] + _balances[i];
        Transfer(0x0, _recipients[i], _balances[i]);
    }
}
2
  • Nice, but it can't work with already existing tokens
    – Davide C
    Commented Feb 21, 2018 at 23:59
  • Or you can use automated solution: multisender.app
    – rstormsf
    Commented May 11, 2019 at 10:06
1

Could just use an automated solution such as BlockBucket which is like a Token MultiSender Dapp with really little fees (0.01 ETH) we built it to offer a cheaper solution to MultiSender which takes about 9x more in terms of fees.

*I'm part of the team that built this Dapp for transparency.

2
  • This is really interesting. I would love to know how you implemented this. There is a limit to how efficient/cheap something can be.
    – Sky
    Commented Jan 16, 2023 at 10:28
  • 1
    Feel free to take a look at the code on Etherscan here: etherscan.io/address/… It's a really simple contract and only has a few functions :) Commented Jan 17, 2023 at 13:00
1

The best approach is a merkle tree airdrop, where the on-chain contains a compressed list of address receivers and the airdrop receiver pays the gas.

See

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.