Forge gas measurement adds a significant overhead when transferring native tokens (ETH) to an account
0xpolarzero opened this issue · 2 comments
Component
Forge
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
forge 0.2.0 (2cf84d9 2024-02-07T00:15:49.638525000Z)
What command(s) is the bug in?
forge test --gas-report
Operating System
macOS (Apple Silicon)
Describe the bug
Overview
When transferring native tokens to a recipient, there is a significant gas overhead, compared to real-world values. Here, we're using GasliteDrop airdropETH
function to measure gas usage.
As a reference, we're measuring the same interactions with Tevm, which shows results extremely close to real-world values (see directly on Etherscan).
Test
The test is as follows:
function test_airdropETH() public {
payable(user).transfer(value);
vm.startPrank(user);
address[] memory recipients = new address[](quantity);
uint256[] memory amounts = new uint256[](quantity);
for (uint256 i = 0; i < quantity; i++) {
recipients[i] = vm.addr(2); // this is what we will modify for comparison
amounts[i] = 0.001 ether;
}
gasliteDrop.airdropETH{value: value}(recipients, amounts);
vm.stopPrank();
}
Comparison
There are 3 different cases:
- airdropping each time to a new address (Foundry);
- airdropping each time to the same address (Foundry);
- airdropping each time to a new address (Tevm, our reference).
Here are the results for each (in the format n (recipients): gas (+ increase
).
Foundry test with the same address each time
Run the original test.
1: 35,132
2: 42,015 (+6,883)
3: 48,898 (+6,883)
…
1000: 6,911,249
Foundry test with a new address for each recipient
Update line 61
- recipients[i] = vm.addr(2); + recipients[i] = vm.addr(i + 1);
1: 35,132
2: 69,515 (+34,383)
3: 103,898 (+34,383)
…
1000: 34,383,749
Tevm (simulate the transaction and return the gas used)
1: 10,132
2: 19,515 (+9,383)
3: 28,898 (+9,383)
…
1000: 9,383,749
These are similar to values found in Etherscan transactions.
So the difference here is:
- Foundry (same recipient) -> Foundry (different recipients): +27,500 gas per recipient;
- Tevm -> Foundry (different recipients): +25,000 gas per recipient;
- Tevm -> Foundry (same recipient): +2,500 gas per recipient.
I can only make a few guesses:
- Foundry considers the cost of "initializing" a new account on the EVM, which adds a 22,500 gas overhead? (25,000 - 2,500);
- Tevm ignores this cost, which is why it is similar to values on actual mainnet transfers, usually to accounts that are already initialized.
But there is still that additional 2,500 gas for each recipient, even when the account has already received some native token previously.
Some measurements with --debug
, just for additional context, at the transfer call, show that:
- the first transfer costs 34,300 gas;
- the second transfer costs the same if it's a new address, or 6,800 gas if it's the same (-27,500).
CALL
s are subject to cold account access (2500 gas) and initialization surcharges (25000 gas); see evm.codes for more info. It's unfortunately strictly always more expensive to send an empty account native tokens via smart contract than via plain transaction (in native gas units, at least).
I'm not sure why TEVM doesn't consider the cold+empty surcharges – but it should, if it's in the context of calling a smart contract like Gaslite Drop.
Thank you for your answer @emo-eth, that makes total sense—didn't know about the cold account access.