2

If I create a token, and then want to distribute it to 1000 people, I am worried about the gas price of those transactions. Which is why I wanted to analyse the different options for distributing and their inherent costs.

option 1: function in token that distributes to an array of users

function distributeToken(address[] addresses, uint256 _value) onlyOwner {
     for (uint i = 0; i < addresses.length; i++) {
         balances[owner] -= _value;
         balances[addresses[i]] += _value;
         Transfer(owner, addresses[i], _value);
     }
}

source: Distribute token to multiple address

ELABORATIONG ON OPTION ONE: is it possible to use parameters with an array of 1000 different addresses? That would be a massively large parameter and I'm curious as to what the gas price would look like.

option 2: using an exchange

This option is very restrictive however. It would allow anyone to buy the coins, and I don't necessarily want them to be bought but given away for free to specific users.

QUESTIONS

  • Are there other options people are aware of?
  • Which option would be most suitable and cost the least amount of gass?

DIFFERENCES BETWEEN THE LINKED DUPLICATE QUESTION AND THIS ONE: ( How to transfer tokens to bounty participants? )

The questions have noticeable differences

  • The details that are being discussed.
  • I also have 2 specific questions at the end which are not identical to that

while the question linked that is similar asks:

  • What is best practice to send ERC20-compliant tokens to bounty participants?

It doesn't provide the specific criteria for what best would refer to, while I do, and I also ask the other available options to make a comparison with. Even though they have similarities, they are not the same.

Webeng
  • 895
  • 2
  • 12
  • 26
  • @ElishaDrion Thanks for the link, though I do believe the questions have noticeable differences, the main one people the details that are being discussed. I also have 2 specific questions at the end which are not identical to that question. Even though they are similar, they are not the same. – Webeng Apr 01 '18 at 14:29

1 Answers1

2

The best way

The best way is quite an objective term.

The most cost effective way is definitely to execute them all in one loop (option 1), although there will be a limit to how many addresses you can perform at once (you'll probably need to do 10 batches of 100 addresses each).

Option 2 may indirectly cost you less in gas, but someone still has to pay the gas, and since they will be (presumably) executing one by one, the overall gas cost will be more, but since you won't be paying it, maybe that's better for you?

A slightly better implementation:

function distributeToken(address[] addresses, uint256 _value) onlyOwner {
     uint total = _value * addresses.length;
     require(total/_value == addresses.length); // Overflow check
     require(balances[owner] >= total); // Underflow check
     balances[owner] -= total;
     for (uint i = 0; i < addresses.length; i++) {
         balances[addresses[i]] += _value;
         require(balances[addresses[i]] >= _value); // Overflow check
         Transfer(owner, addresses[i], _value);
     }
}

Gas estimations:

For single calls to a typical transfer() function, it's the same cost per call, which means that for each address you'll be paying 23,500 gas.

However when you perform a bulk update instead, you save the overhead of the invocation cost and just have to pay the cost of the loop and storage etc. After a base cost of 27,947 gas, each address only costs an extra 9,703 gas.

┌─────┬────────────┬─────────────────┐
│  #  │  transfer  │ distributeToken │
├─────┼────────────┼─────────────────┤
│   1 │     23,500 │          37,650 │
│   2 │     47,000 │          47,353 │
│   3 │     70,500 │          57,056 │
│  10 │    235,000 │         124,977 │
│  50 │  1,175,000 │         513,097 │
│ 500 │ 11,750,000 │       4,879,447 │
└─────┴────────────┴─────────────────┘

That means after 2 addresses it's cheaper.

For 8 Mgas (maximum for a single call), you could do around 821 addresses:

27,947 base call gas + 9,703 gas/address * 821 addresses = 7,994,110 gas
supakaity
  • 1,468
  • 7
  • 16
  • Why not use the transfer function of the token, beside for sparing JUMP instructions? – Elisha Drion Apr 01 '18 at 14:34
  • Generally airdrop functions tend to want to be as efficient as possible. Calling the transfer function is also usually a public function which is quite a bit more expensive than jumping to an internal transfer function, but still managing it within an single function even saves that cost. – supakaity Apr 01 '18 at 14:37
  • very interesting, I wonder how much difference in gas this would bring. For instance if sending to 100 people with the distributeToken function costs the same as sending to 2 people through the ERC-20 transfer function – Webeng Apr 01 '18 at 14:38
  • There's a minimum cost of 21,000 gas per call, so even at absolute minimum cost (excluding storage costs etc), 2 calls to transfer() would cost 42,000 gas. – supakaity Apr 01 '18 at 14:44
  • Yeah, I realized that, but I wonder if the differences are that big, if it's worth the repetition of code. – Elisha Drion Apr 01 '18 at 15:23
  • Just as an idea, doing it this way (calling transfer() instead of coding it into the loop) would result in a cheaper base cost 22,239, but each address would add 13,978 gas/address, so: 1 address = 36,117; 2 = 50,095; 3 = 64,073 and so on. – supakaity Apr 01 '18 at 15:54
  • To be fair, reducing the code footprint would save 75,539 gas on creation though, so that needs to be taken into account. At 1000 addresses, the savings on the combined loop would be 4,275,000, which means you'd be ahead by about 4.2 Mgas. – supakaity Apr 01 '18 at 16:03
  • thanks so much man, you really put a lot of detail in your answer, really appreciate it – Webeng Apr 01 '18 at 16:23
  • Could I ask how you found the base values? Was it through testing through remix? and if so, would you have any guidelines or links to tutorials that might show the process? – Webeng Apr 01 '18 at 16:23
  • You just put the code into remix, and click the "Details" button when you execute the functions. The number you're looking for is transaction cost which is the actual cost of executing the call. Then you just copy that number, and try a different way, then simple maths tells you if the new way is better or worse. – supakaity Apr 01 '18 at 16:27