Skip to content

Commit b47e3bd

Browse files
fix(evmstate/test): stabilize trace tx tests with deterministic ERC20 transfer recipient (#2418)
* fix(evmstate/test): fix flaky trace tx tests with deterministic ERC20 transfer recipient * changelog
1 parent 09e58ab commit b47e3bd

File tree

3 files changed

+43
-8
lines changed

3 files changed

+43
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ changelog format](https://keepachangelog.com/en/1.0.0/).
4747
See https://github.com/dangoslen/changelog-enforcer.
4848
-->
4949

50-
## [v2.8.0](https://github.com/NibiruChain/nibiru/releases/tag/v2.8.0) - 2025-10-24
50+
- [#2418](https://github.com/NibiruChain/nibiru/pull/2418) - fix(evmstate/test): stabilize trace tx tests with deterministic ERC20 transfer recipient
51+
52+
## [v2.8.0](https://github.com/NibiruChain/nibiru/releases/tag/v2.8.0) - 2025-10-28
5153

5254
- [#2385](https://github.com/NibiruChain/nibiru/pull/2385) - evm: 63/64 gas clamp for ERC20 calls; improved VM error surfacing; add composite Chainlink-like oracle
5355
- [#2388](https://github.com/NibiruChain/nibiru/pull/2388) - chore: erc20 token registry new tokens: cbBTC, uBTC

x/evm/evmstate/grpc_query_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ func TraceNibiTransfer() string {
5252
func TraceResCallTracer_ERC20Transfer(
5353
fromAddr, toAddr gethcommon.Address,
5454
) (traceResFields map[string]string) {
55-
gas := hexutil.Uint64(51_250)
56-
gasUsed := hexutil.Uint64(34_150)
55+
gas := hexutil.Uint64(51_046) // "0xc766"
56+
gasUsed := hexutil.Uint64(33_946) // "0x849a"
5757

5858
f := make(map[string]string)
5959
f["from"] = fromAddr.Hex()

x/evm/evmtest/tx.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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
103120
func 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

Comments
 (0)