@@ -99,14 +99,47 @@ func DeployContract(
9999 }, nil
100100}
101101
102- // DeployAndExecuteERC20Transfer deploys contract, executes transfer and returns tx hash
102+ // DeployAndExecuteERC20Transfer deploys a test ERC-20, executes one transfer,
103+ // and returns the transfer tx, the deploy tx (as a predecessor), and the
104+ // contract address.
105+ //
106+ // ## Determinism: Why we use a fixed recipient address
107+ //
108+ // Intrinsic gas charges depend on calldata bytes. Since Istanbul (EIP-2028),
109+ // each zero byte is 4 gas and each non-zero byte is 16 gas (+12 per non-zero
110+ // byte). In an ERC-20 transfer(address,uint256), the address occupies the last
111+ // 20 bytes of a 32-byte word (left-padded with zeros). Random recipients change
112+ // the count of non-zero bytes and make gas/gasUsed assertions flaky.
113+ //
114+ // To keep tests stable, this helper uses a fixed recipient address whose byte
115+ // pattern is constant. The intrinsic gas term is then reproducible.
116+ //
117+ // References:
118+ // - EIP-2028: https://eips.ethereum.org/EIPS/eip-2028
119+ // - Solidity ABI padding rules: https://docs.soliditylang.org/en/latest/abi-spec.html
103120func DeployAndExecuteERC20Transfer (
104121 deps * TestDeps , t * testing.T ,
105122) (
106- erc20Transfer * evm.MsgEthereumTx ,
107- predecessors []* evm.MsgEthereumTx ,
108- contractAddr gethcommon.Address ,
123+ erc20Transfer * evm.MsgEthereumTx , // the signed transfer tx
124+ predecessors []* evm.MsgEthereumTx , // slice containing the deploy tx
125+ contractAddr gethcommon.Address , // ERC20 token being transferred
109126) {
127+ var (
128+ // Use a deterministic recipient to avoid nondeterministic intrinsic gas from calldata
129+ // (non-zero vs zero byte costs). A fixed address ensures stable gas/gasUsed in traces.
130+ //
131+ // This address, "0x0000000000000000000000000000000000010f2C", has only 3
132+ // non-zero bytes in its 20-byte payload (01, 0f, 2c). This means we expect
133+ // it to have lower intrinsic gas than a "typical" random address that has
134+ // ~20 non-zero bytes. Lower by (20 - 3) * 12 = 204 gas units, based on
135+ // EIP-2028 (Istanbul upgrade).
136+ //
137+ // Istanbul reduced the calldata charge to 4 gas
138+ // per zero byte and 16 per non-zero byte, which is why changing the address
139+ // bytes moves intrinsic gas in steps of 12.
140+ fixedRecipient = gethcommon .BigToAddress (big .NewInt (69_420 ))
141+ )
142+
110143 // TX 1: Deploy ERC-20 contract
111144 deployResp , err := DeployContract (deps , embeds .SmartContract_TestERC20 )
112145 require .NoError (t , err )
@@ -122,7 +155,7 @@ func DeployAndExecuteERC20Transfer(
122155
123156 // TX 2: execute ERC-20 contract transfer
124157 input , err := contractData .ABI .Pack (
125- "transfer" , NewEthPrivAcc (). EthAddr , new (big.Int ).SetUint64 (1000 ),
158+ "transfer" , fixedRecipient , new (big.Int ).SetUint64 (1000 ),
126159 )
127160 require .NoError (t , err )
128161 nonce = deps .NewStateDB ().GetNonce (deps .Sender .EthAddr )
0 commit comments