From 3dfe0aff1736d3a8a51186db29f309d205013072 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:37:23 +0000 Subject: [PATCH 01/31] execution: implement EIP-7976 Raise the calldata floor cost from 10/40 (EIP-7623) to a uniform 64 gas per byte, activated with the Amsterdam (Glamsterdam) fork. This reduces the maximum possible block size by ~37% with minimal user impact. Co-Authored-By: Claude Opus 4.6 (1M context) --- execution/protocol/mdgas/EIP-7976.md | 52 ++++++++ execution/protocol/mdgas/intrinsic_gas.go | 20 ++- .../protocol/mdgas/intrinsic_gas_test.go | 122 ++++++++++++++++++ execution/protocol/params/protocol.go | 11 +- execution/protocol/txn_executor.go | 1 + .../tests/testutil/transaction_test_util.go | 1 + execution/types/aa_transaction.go | 1 + txnprovider/txpool/pool.go | 2 + 8 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 execution/protocol/mdgas/EIP-7976.md diff --git a/execution/protocol/mdgas/EIP-7976.md b/execution/protocol/mdgas/EIP-7976.md new file mode 100644 index 00000000000..29ba5a59ac0 --- /dev/null +++ b/execution/protocol/mdgas/EIP-7976.md @@ -0,0 +1,52 @@ +# EIP-7976: Increase Calldata Floor Cost + +**Spec:** https://eips.ethereum.org/EIPS/eip-7976 +**Depends on:** EIP-7623 (Prague) +**Activated by:** Amsterdam (Glamsterdam) fork + +## Summary + +EIP-7976 modifies the calldata floor cost mechanism introduced by EIP-7623. It raises the floor from 10/40 gas per zero/non-zero byte to a uniform 64 gas per byte, reducing maximum possible block size by ~37%. + +## Parameters + +| Parameter | EIP-7623 (Prague) | EIP-7976 (Amsterdam) | +|-----------|-------------------|----------------------| +| `TOTAL_COST_FLOOR_PER_TOKEN` | 10 | 16 | +| Floor token formula | `zero_bytes + 4 * nonzero_bytes` | `(zero_bytes + nonzero_bytes) * 4` | +| Floor per zero byte | 10 | 64 | +| Floor per non-zero byte | 40 | 64 | + +Standard calldata pricing (4/16 per zero/non-zero byte) is unchanged. + +## Gas Formula + +``` +tx.gasUsed = 21000 + max( + standard_calldata_gas + execution_gas + creation_overhead, + TOTAL_COST_FLOOR_PER_TOKEN * floor_tokens_in_calldata +) +``` + +Where: +- `standard_calldata_gas = 4 * zero_bytes + 16 * nonzero_bytes` (unchanged) +- `floor_tokens_in_calldata = total_bytes * 4` (EIP-7976) + +## Implementation Files + +| File | Change | +|------|--------| +| `execution/protocol/params/protocol.go` | `TxTotalCostFloorPerTokenEIP7976 = 16` constant | +| `execution/protocol/mdgas/intrinsic_gas.go` | `IsEIP7976` flag + floor formula | +| `execution/protocol/txn_executor.go` | `IsEIP7976: rules.IsAmsterdam` | +| `execution/types/aa_transaction.go` | `IsEIP7976: rules.IsAmsterdam` | +| `execution/tests/testutil/transaction_test_util.go` | `IsEIP7976: rules.IsAmsterdam` | +| `txnprovider/txpool/pool.go` | `IsEIP7976: p.isAmsterdam()` (2 sites) | + +## What Doesn't Change + +- Chain config / Rules struct (Amsterdam already exists) +- Instruction sets / opcodes +- `eth_estimateGas` (uses same intrinsic gas path) +- Refund / gas-used logic (already uses `FloorGasCost` via `max()`) +- Transaction validity check (already validates `gas >= FloorGasCost`) diff --git a/execution/protocol/mdgas/intrinsic_gas.go b/execution/protocol/mdgas/intrinsic_gas.go index 9fca28361bd..61b4e1bbe26 100644 --- a/execution/protocol/mdgas/intrinsic_gas.go +++ b/execution/protocol/mdgas/intrinsic_gas.go @@ -36,6 +36,7 @@ type IntrinsicGasCalcArgs struct { IsEIP2028 bool IsEIP3860 bool IsEIP7623 bool + IsEIP7976 bool IsEIP8037 bool IsAATxn bool } @@ -125,8 +126,23 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) } if args.IsEIP7623 { - tokenLen := dataLen + 3*nz - dataGas, overflow := math.SafeMul(tokenLen, params.TxTotalCostFloorPerToken) + var tokenLen uint64 + var costPerToken uint64 + var overflow bool + if args.IsEIP7976 { + // EIP-7976: floor_tokens = total_bytes * 4, cost_per_token = 16 + // => 64 gas per byte (both zero and non-zero) + tokenLen, overflow = math.SafeMul(dataLen, 4) + if overflow { + return IntrinsicGasCalcResult{}, true + } + costPerToken = params.TxTotalCostFloorPerTokenEIP7976 + } else { + // EIP-7623: tokens = zero_bytes + 4*nonzero_bytes = dataLen + 3*nz + tokenLen = dataLen + 3*nz + costPerToken = params.TxTotalCostFloorPerToken + } + dataGas, overflow := math.SafeMul(tokenLen, costPerToken) if overflow { return IntrinsicGasCalcResult{}, true } diff --git a/execution/protocol/mdgas/intrinsic_gas_test.go b/execution/protocol/mdgas/intrinsic_gas_test.go index ebc00e1f143..a4ff13baa0e 100644 --- a/execution/protocol/mdgas/intrinsic_gas_test.go +++ b/execution/protocol/mdgas/intrinsic_gas_test.go @@ -108,3 +108,125 @@ func TestZeroDataIntrinsicGas(t *testing.T) { assert.Equal(params.TxGas, result.RegularGas) assert.Equal(params.TxGas, result.FloorGasCost) } + +func TestEIP7976FloorCost(t *testing.T) { + // EIP-7976 floor: 64 gas per byte (both zero and non-zero), + // computed as floor_tokens = total_bytes * 4, cost_per_token = 16. + cases := map[string]struct { + dataLen uint64 + dataNonZeroLen uint64 + expectedFloor uint64 + }{ + "zero data": { + dataLen: 0, + dataNonZeroLen: 0, + expectedFloor: params.TxGas, // 21000, no floor addition + }, + "all zero bytes": { + // 32 zero bytes: floor_tokens = 32*4 = 128, floor = 128*16 = 2048 + dataLen: 32, + dataNonZeroLen: 0, + expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 + }, + "all non-zero bytes": { + // 32 non-zero bytes: floor_tokens = 32*4 = 128, floor = 128*16 = 2048 + // Key property: same floor as all-zero (byte value doesn't matter) + dataLen: 32, + dataNonZeroLen: 32, + expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 + }, + "mixed bytes": { + // 20 zero + 12 non-zero = 32 total: floor_tokens = 32*4 = 128, floor = 128*16 = 2048 + dataLen: 32, + dataNonZeroLen: 12, + expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 + }, + "single byte non-zero": { + dataLen: 1, + dataNonZeroLen: 1, + expectedFloor: params.TxGas + 1*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 64 = 21064 + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + result, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, c.dataLen), + DataNonZeroLen: c.dataNonZeroLen, + IsEIP2: true, + IsEIP2028: true, + IsEIP3860: true, + IsEIP7623: true, + IsEIP7976: true, + }) + assert.False(overflow) + assert.Equal(c.expectedFloor, result.FloorGasCost, + "EIP-7976 floor mismatch") + }) + } +} + +func TestEIP7976VsEIP7623Floor(t *testing.T) { + // Compare EIP-7976 vs EIP-7623 floor costs for the same data. + // EIP-7976 should always be >= EIP-7623 for zero bytes, + // and equal or different for non-zero bytes. + assert := assert.New(t) + + // 32 non-zero bytes: + // EIP-7623: tokens = 32 + 3*32 = 128, floor = 128*10 = 1280 + // EIP-7976: tokens = 32*4 = 128, floor = 128*16 = 2048 + result7623, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, 32), + DataNonZeroLen: 32, + IsEIP2: true, + IsEIP2028: true, + IsEIP7623: true, + }) + assert.False(overflow) + + result7976, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, 32), + DataNonZeroLen: 32, + IsEIP2: true, + IsEIP2028: true, + IsEIP7623: true, + IsEIP7976: true, + }) + assert.False(overflow) + + assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerToken, result7623.FloorGasCost) // 21000+1280=22280 + assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerTokenEIP7976, result7976.FloorGasCost) // 21000+2048=23048 + assert.Greater(result7976.FloorGasCost, result7623.FloorGasCost) + + // 32 zero bytes: + // EIP-7623: tokens = 32 + 3*0 = 32, floor = 32*10 = 320 + // EIP-7976: tokens = 32*4 = 128, floor = 128*16 = 2048 + result7623z, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, 32), + DataNonZeroLen: 0, + IsEIP2: true, + IsEIP2028: true, + IsEIP7623: true, + }) + assert.False(overflow) + + result7976z, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, 32), + DataNonZeroLen: 0, + IsEIP2: true, + IsEIP2028: true, + IsEIP7623: true, + IsEIP7976: true, + }) + assert.False(overflow) + + assert.Equal(params.TxGas+32*params.TxTotalCostFloorPerToken, result7623z.FloorGasCost) // 21000+320=21320 + assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerTokenEIP7976, result7976z.FloorGasCost) // 21000+2048=23048 + assert.Greater(result7976z.FloorGasCost, result7623z.FloorGasCost) + + // Standard gas should be the same regardless of EIP-7976 + assert.Equal(result7623.RegularGas, result7976.RegularGas) + assert.Equal(result7623z.RegularGas, result7976z.RegularGas) +} diff --git a/execution/protocol/params/protocol.go b/execution/protocol/params/protocol.go index ab44e037ec5..4ca4dac192e 100644 --- a/execution/protocol/params/protocol.go +++ b/execution/protocol/params/protocol.go @@ -94,11 +94,12 @@ const ( SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. - TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) - TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list - TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list - TxTotalCostFloorPerToken uint64 = 10 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7623) + TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list + TxTotalCostFloorPerToken uint64 = 10 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7623) + TxTotalCostFloorPerTokenEIP7976 uint64 = 16 // Per token of calldata floor cost (EIP-7976), replaces EIP-7623 value // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index 6783721cc22..148a68c8bad 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -839,6 +839,7 @@ func (st *TxnExecutor) calcIntrinsicGas(contractCreation bool, auths []types.Aut IsEIP2028: rules.IsIstanbul, IsEIP3860: vmConfig.HasEip3860(rules), IsEIP7623: rules.IsPrague, + IsEIP7976: rules.IsAmsterdam, IsEIP8037: rules.IsAmsterdam, }) } diff --git a/execution/tests/testutil/transaction_test_util.go b/execution/tests/testutil/transaction_test_util.go index 76b8c587e34..a0a4b712b93 100644 --- a/execution/tests/testutil/transaction_test_util.go +++ b/execution/tests/testutil/transaction_test_util.go @@ -90,6 +90,7 @@ func (tt *TransactionTest) Run(chainID *big.Int) error { IsEIP2028: rules.IsIstanbul, IsEIP3860: rules.IsShanghai, IsEIP7623: rules.IsPrague, + IsEIP7976: rules.IsAmsterdam, IsEIP8037: rules.IsAmsterdam, }) requiredGas := intrinsicGasResult.RegularGas diff --git a/execution/types/aa_transaction.go b/execution/types/aa_transaction.go index a3664837a1d..4cecd93c04a 100644 --- a/execution/types/aa_transaction.go +++ b/execution/types/aa_transaction.go @@ -501,6 +501,7 @@ func (tx *AccountAbstractionTransaction) PreTransactionGasCost(rules *chain.Rule IsEIP2028: rules.IsIstanbul, IsEIP3860: hasEIP3860, IsEIP7623: rules.IsPrague, + IsEIP7976: rules.IsAmsterdam, IsEIP8037: rules.IsAmsterdam, IsAATxn: true, }) diff --git a/txnprovider/txpool/pool.go b/txnprovider/txpool/pool.go index 806a20ab16b..a6e249876cc 100644 --- a/txnprovider/txpool/pool.go +++ b/txnprovider/txpool/pool.go @@ -793,6 +793,7 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf uint64, IsEIP2028: true, IsEIP3860: isEIP3860, IsEIP7623: isEIP7623, + IsEIP7976: p.isAmsterdam(), IsEIP8037: p.isAmsterdam(), IsAATxn: isAATxn, }) @@ -982,6 +983,7 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache IsEIP2028: true, IsEIP3860: isEIP3860, IsEIP7623: isPrague, + IsEIP7976: p.isAmsterdam(), IsEIP8037: p.isAmsterdam(), IsAATxn: isAATxn, }) From fcaa52ef7eeb67c5ac711d9d6f62df06ae282cb3 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Fri, 17 Apr 2026 07:17:47 +0000 Subject: [PATCH 02/31] execution: remove EIP-7976 spec doc from repo Move to gitignored agentspecs/ directory instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- execution/protocol/mdgas/EIP-7976.md | 52 ---------------------------- 1 file changed, 52 deletions(-) delete mode 100644 execution/protocol/mdgas/EIP-7976.md diff --git a/execution/protocol/mdgas/EIP-7976.md b/execution/protocol/mdgas/EIP-7976.md deleted file mode 100644 index 29ba5a59ac0..00000000000 --- a/execution/protocol/mdgas/EIP-7976.md +++ /dev/null @@ -1,52 +0,0 @@ -# EIP-7976: Increase Calldata Floor Cost - -**Spec:** https://eips.ethereum.org/EIPS/eip-7976 -**Depends on:** EIP-7623 (Prague) -**Activated by:** Amsterdam (Glamsterdam) fork - -## Summary - -EIP-7976 modifies the calldata floor cost mechanism introduced by EIP-7623. It raises the floor from 10/40 gas per zero/non-zero byte to a uniform 64 gas per byte, reducing maximum possible block size by ~37%. - -## Parameters - -| Parameter | EIP-7623 (Prague) | EIP-7976 (Amsterdam) | -|-----------|-------------------|----------------------| -| `TOTAL_COST_FLOOR_PER_TOKEN` | 10 | 16 | -| Floor token formula | `zero_bytes + 4 * nonzero_bytes` | `(zero_bytes + nonzero_bytes) * 4` | -| Floor per zero byte | 10 | 64 | -| Floor per non-zero byte | 40 | 64 | - -Standard calldata pricing (4/16 per zero/non-zero byte) is unchanged. - -## Gas Formula - -``` -tx.gasUsed = 21000 + max( - standard_calldata_gas + execution_gas + creation_overhead, - TOTAL_COST_FLOOR_PER_TOKEN * floor_tokens_in_calldata -) -``` - -Where: -- `standard_calldata_gas = 4 * zero_bytes + 16 * nonzero_bytes` (unchanged) -- `floor_tokens_in_calldata = total_bytes * 4` (EIP-7976) - -## Implementation Files - -| File | Change | -|------|--------| -| `execution/protocol/params/protocol.go` | `TxTotalCostFloorPerTokenEIP7976 = 16` constant | -| `execution/protocol/mdgas/intrinsic_gas.go` | `IsEIP7976` flag + floor formula | -| `execution/protocol/txn_executor.go` | `IsEIP7976: rules.IsAmsterdam` | -| `execution/types/aa_transaction.go` | `IsEIP7976: rules.IsAmsterdam` | -| `execution/tests/testutil/transaction_test_util.go` | `IsEIP7976: rules.IsAmsterdam` | -| `txnprovider/txpool/pool.go` | `IsEIP7976: p.isAmsterdam()` (2 sites) | - -## What Doesn't Change - -- Chain config / Rules struct (Amsterdam already exists) -- Instruction sets / opcodes -- `eth_estimateGas` (uses same intrinsic gas path) -- Refund / gas-used logic (already uses `FloorGasCost` via `max()`) -- Transaction validity check (already validates `gas >= FloorGasCost`) From a750d6a108f2c60aac28126a06232e906a6b0719 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Fri, 17 Apr 2026 07:23:08 +0000 Subject: [PATCH 03/31] txnprovider/txpool: cache isAmsterdam() in TxPool.best loop Avoid calling p.isAmsterdam() twice per iteration. Co-Authored-By: Claude Opus 4.6 (1M context) --- txnprovider/txpool/pool.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/txnprovider/txpool/pool.go b/txnprovider/txpool/pool.go index a6e249876cc..20d7627f4ee 100644 --- a/txnprovider/txpool/pool.go +++ b/txnprovider/txpool/pool.go @@ -716,6 +716,7 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf uint64, isEIP3860 := p.isShanghai() || p.isAgra() isEIP7623 := p.isPrague() || p.isBhilai() + isAmsterdam := p.isAmsterdam() txns.Resize(uint(min(n, len(best.ms)))) var toRemove []*metaTxn @@ -793,8 +794,8 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf uint64, IsEIP2028: true, IsEIP3860: isEIP3860, IsEIP7623: isEIP7623, - IsEIP7976: p.isAmsterdam(), - IsEIP8037: p.isAmsterdam(), + IsEIP7976: isAmsterdam, + IsEIP8037: isAmsterdam, IsAATxn: isAATxn, }) intrinsicRegularGas := intrinsicGasResult.RegularGas From f980e980eb5ecb4c8f118a4b237eb42507d8f417 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Fri, 17 Apr 2026 07:24:44 +0000 Subject: [PATCH 04/31] txnprovider/txpool: cache isAmsterdam() in validateTx Avoid calling p.isAmsterdam() three times; use a single local variable. Co-Authored-By: Claude Opus 4.6 (1M context) --- txnprovider/txpool/pool.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/txnprovider/txpool/pool.go b/txnprovider/txpool/pool.go index 20d7627f4ee..2844437eb87 100644 --- a/txnprovider/txpool/pool.go +++ b/txnprovider/txpool/pool.go @@ -929,7 +929,8 @@ func toBlobs(_blobs [][]byte) []*goethkzg.Blob { func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.CacheView) txpoolcfg.DiscardReason { isEIP3860 := p.isShanghai() || p.isAgra() isPrague := p.isPrague() || p.isBhilai() - isEIP7954 := p.isAmsterdam() + isAmsterdam := p.isAmsterdam() + isEIP7954 := isAmsterdam if txn.IsCreation() { if err := vm.CheckMaxInitCodeSize(uint64(txn.GetDataLen()), isEIP3860, isEIP7954); err != nil { return txpoolcfg.InitCodeTooLarge @@ -984,8 +985,8 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache IsEIP2028: true, IsEIP3860: isEIP3860, IsEIP7623: isPrague, - IsEIP7976: p.isAmsterdam(), - IsEIP8037: p.isAmsterdam(), + IsEIP7976: isAmsterdam, + IsEIP8037: isAmsterdam, IsAATxn: isAATxn, }) gas := mdgas.MdGas{ @@ -1021,7 +1022,7 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache // EIP-8037 (Amsterdam): TX_MAX_GAS_LIMIT applies to the regular gas dimension only. // Pre-Amsterdam: cap = full tx gas limit. var gasToCap uint64 - if p.isAmsterdam() { + if isAmsterdam { gasToCap = max(intrinsicGasResult.RegularGas, intrinsicGasResult.FloorGasCost) } else { gasToCap = txn.GetGas() From 4fc458d4bfbd2dd2bb209a690e677a628689192f Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Fri, 17 Apr 2026 09:13:29 +0000 Subject: [PATCH 05/31] empty test From 1e6237fb098c315289f15ab01ea6b072bad45fe7 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:08:45 +0000 Subject: [PATCH 06/31] claude: add skill for implementing a new eip --- .claude/skills/erigon-implement-eip/SKILL.md | 156 +++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .claude/skills/erigon-implement-eip/SKILL.md diff --git a/.claude/skills/erigon-implement-eip/SKILL.md b/.claude/skills/erigon-implement-eip/SKILL.md new file mode 100644 index 00000000000..f127421aa8b --- /dev/null +++ b/.claude/skills/erigon-implement-eip/SKILL.md @@ -0,0 +1,156 @@ +--- +name: erigon-implement-eip +description: Implement a new EIP for a hardfork under development in Erigon. Use when the user asks to implement, port, or wire up an EIP — covers spec lookup, dep analysis, prior-work check, implementation, lint, tests, and a wrap-up saved to `agentspecs/`. +argument-hint: " " +allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, Skill +--- + +# Implement EIP-$1 in the $0 fork + +## Step 1 — Locate and understand the EIP specification + +Fetch the specification from: + +``` +https://eips.ethereum.org/EIPS/eip-$1 +``` + +Read the spec end-to-end and make sure you understand it. If there are any ambiguities or things that do not make sense in the EIP, bring these up and ask for clarifications. Once understood, map the EIP to the codebase — identify which packages, files, and existing constructs the EIP will touch. + +## Step 2 — Find dependent and referenced EIPs + +Identify any other EIPs that EIP-$1 depends on or references. Each such EIP can be fetched using the same URL pattern as in Step 1, substituting its number: + +``` +https://eips.ethereum.org/EIPS/eip- +``` + +Try to understand these additional EIPs and map them to the code. If there are any ambiguities or contradictions with the new EIP, make sure to raise these and ask questions. + +## Step 3 — Identify the hardforks of referenced / interplaying EIPs + +Identify which hardforks contain the referenced EIPs, as well as any other EIPs already in the codebase that interplay with the new one. This provides more context and knowledge when analysing the code. + +A list of forks and the EIPs they contain can be found at: + +``` +https://eips.ethereum.org/meta +``` + +They are organised as "meta EIPs" and usually start with `Hardfork Meta` followed by the name of the hardfork. + +### Fork naming + +More recent forks use portmanteau naming to combine the execution-layer (EL) fork name and the consensus-layer (CL) fork name. EL forks are named after cities; CL forks are named after stars. For example, **Glamsterdam** is a portmanteau of **Gloas** (CL) and **Amsterdam** (EL). + +### EL vs CL scope + +Some EIPs only affect the EL, others only the CL, and some affect both. Erigon has both an EL implementation (under the `execution` package) and a CL implementation (under the `cl` package), so all EIPs apply to one or both layers in Erigon. + +### Reading a meta EIP + +In some cases fork meta EIPs list which EIPs got included; in other cases they provide a summary of the changes instead of an EIP list. + +The meta EIP for the **current fork under development** usually has these lists: +- **CFI** — EIPs Considered For Inclusion +- **SFI** — EIPs Scheduled For Inclusion +- **PFI** — EIPs Proposed For Inclusion +- **DFI** — EIPs Declined For Inclusion + +## Step 4 — Reference the Ethereum Yellow Paper + +Reference the Ethereum Yellow Paper for deeper information about the Ethereum protocol. It can be found at: + +``` +https://ethereum.github.io/yellowpaper/paper.pdf +``` + +Note however that the current version of it covers up to and including the Shanghai hard fork. Changes to the protocol after Shanghai can be inferred via the meta EIPs for the subsequent forks after Shanghai. + +The yellow paper provides deep base knowledge that can be used to confirm correctness up to Shanghai, and later for parts that haven't been changed by subsequent hard forks. For parts that have been changed in subsequent hard forks, the corresponding EIP specs from those hardforks — read in chronological order — become the main source of truth. + +## Step 5 — Check for previous work on this EIP + +Check whether any previous work for this EIP has already been done in the codebase. This may happen if we've previously implemented this EIP but against an older / outdated spec for a previous devnet. + +In this case, analyse the current code and compare it to the latest specs. Implement the necessary changes to address any discrepancies that you find. + +## Step 6 — Implement the change + +Implement the EIP changes in the code, based on the understanding gathered in Steps 1–5 and the codebase mapping you produced. Touch the packages, files, and existing constructs identified during that mapping. + +If during implementation you discover something that is unclear or appears to contradict the spec, stop and ask for clarification rather than guessing. + +After making code changes, run `make lint` and fix any reported issues. The linter is non-deterministic — run it repeatedly until it is clean. + +## Step 7 — Run local tests + +Run local tests using the `/erigon-test-all` skill. Analyse and fix any failures **in the implementation** (see also "Question the tests — do not silently fix them" below). Keep iterating until all tests are fixed. + +### Which EL tests matter most + +The most important tests when implementing a new EIP for the EL are the tests under the `execution/tests` package. More specifically: + +- `TestExecutionSpecBlockchainDevnet` — covers the current devnet tests for the hardfork under development. +- All the other `TestExecutionSpecBlockchain*` variants — cover stable hard forks (the main `TestExecutionSpecBlockchain` plus per-fork specialised variants like `…CancunBlobs`, `…PragueCalldata`, `…OsakaCLZ`, `…FrontierScenarios`). + +### Where the test fixtures come from + +These protocol tests are defined by JSON test fixture files sourced from the `execution/tests/execution-spec-tests` directory. It is sourced as a git submodule which the `/erigon-test-all` skill knows how to keep in sync. + +These test fixture files are generated by the Ethereum Python spec reference implementation, which can be found at: + +``` +https://github.com/ethereum/execution-specs +``` + +The code version which generates the test fixtures for a given hard fork lives on a branch following the `forks/` naming convention. For example, if the fork name is `osaka` then the branch name will be `forks/osaka`. The branch for the user-provided hard fork under development is `forks/$0`. + +Sometimes new EIPs under development may not be in the `forks/$0` branch yet — they may be on a separate feature branch. To find it, look for a branch name that contains the provided EIP number `$1`. Once you find a branch (or several branches), ask the user to confirm which one to use. + +The point of identifying the correct branch is to **read and analyse the right reference code** — i.e. the version of the Python spec implementation that corresponds to the fixtures we are testing against. Nothing in the local repo needs to be updated; this is purely about which code version the agent downloads / reads for cross-referencing while debugging or validating behaviour. + +### Question the tests — do not silently fix them + +It is **VERY IMPORTANT** to question the tests and validate they are doing the correct thing as per the EIP specifications. This applies to both EL spec tests (driven by `execution/tests/execution-spec-tests`) and CL spec tests (driven by `cl/spectest` — see "Running CL spec tests" below). + +Sometimes some of the protocol spec tests may have bugs or mistakes in them due to inaccurate implementation in the upstream reference implementations (the Python `execution-specs` for the EL, the `consensus-spec-tests` for the CL). + +Or some of our other local tests (outside of the ones driven by `execution/tests/execution-spec-tests`, `execution/tests/legacy-tests`, or the `cl/spectest` fixtures) may have gaps in them, test for the wrong thing, or test for logic from before the new EIP — in which case the test should be amended to be explicitly for the correct corresponding previous hardfork, and a new test should be written for the new hardfork which this EIP will go into, covering both the old logic (prior to this EIP) and the new logic (after it). + +When you run into such situations **do not attempt to fix those tests**. Instead write a summary of your findings and ask for them to be reviewed and new guidance to be given. + +### Running CL spec tests + +If the EIP affects the CL (or has any CL-side touchpoints), also run the consensus-spec tests under `cl/spectest`. These mirror what the `Consensus spec` GitHub workflow (`.github/workflows/test-integration-caplin.yml`) runs. + +The flow is: + +``` +cd cl/spectest +make tests # downloads the consensus-spec-tests fixture tarball and unpacks it +make mainnet # runs the full mainnet CL spec test suite +``` + +For faster iteration when only one fork is relevant, the `cl/spectest/Makefile` exposes per-fork targets such as `make capella`, `make deneb`, `make electra`, `make fulu`, etc. Use the one matching the fork the EIP belongs to. If no per-fork target exists yet for the new fork, add one or run `CGO_CFLAGS=-D__BLST_PORTABLE__ go test -run=/mainnet// -v --timeout 30m` from `cl/spectest` (the `CGO_CFLAGS` flag mirrors the Makefile and is required for the BLS portable build). + +If `make tests` fails (e.g. fixture download issue), the CI workflow treats it as a soft skip — but locally you should investigate and fix it before relying on the CL test results. + +## Step 8 — Wrap up + +Once implementation is complete and tests are passing, produce a wrap-up summary of all the work done. The summary should cover: + +- The EIP being implemented and the fork it targets +- All packages, files, and constructs that were touched +- Key design decisions and any deviations from the spec (with justification) +- Dependent / referenced EIPs that influenced the implementation +- Any outstanding questions, unresolved ambiguities, or test discrepancies that were flagged for review +- Test coverage status — which `TestExecutionSpecBlockchain*` tests now pass (and CL `cl/spectest` results, if applicable), and any that were skipped or remain open + +Include the summary in your final reply, and also save it as a markdown file at: + +``` +agentspecs/eip-$1-implementation/summary.md +``` + +Create the `agentspecs/eip-$1-implementation/` directory if it does not already exist. The `agentspecs/` directory is gitignored, so these notes stay local. From 492ed4352e1f3c36719ebd5e0f8c517caaf3b0b4 Mon Sep 17 00:00:00 2001 From: yperbasis Date: Fri, 17 Apr 2026 22:36:23 +0200 Subject: [PATCH 07/31] bal-devnet-4: enable ExperimentalBAL and Exec3Parallel by default Flip the devnet-only defaults for the bal-devnet-4 branch: - ExperimentalBAL: false -> true (ethconfig.Defaults, CLI flag, tester) - EXEC3_PARALLEL env default: false -> true Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/erigon/node/config_snapshot_test.go | 4 ++-- common/dbg/experiments.go | 2 +- execution/execmodule/execmoduletester/exec_module_tester.go | 2 +- node/cli/flags.go | 2 +- node/ethconfig/config.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/erigon/node/config_snapshot_test.go b/cmd/erigon/node/config_snapshot_test.go index daef72bea6d..91bdb790867 100644 --- a/cmd/erigon/node/config_snapshot_test.go +++ b/cmd/erigon/node/config_snapshot_test.go @@ -116,7 +116,7 @@ func TestConfigDefaults(t *testing.T) { require.Equal(t, uint64(1), snap.NetworkID, "default network should be mainnet") require.True(t, snap.StateStream, "state stream should be enabled by default") require.True(t, snap.InternalCL, "internal CL (Caplin) is on by default") - require.False(t, snap.ExperimentalBAL, "experimental BAL should be off by default") + require.True(t, snap.ExperimentalBAL, "experimental BAL should be on by default on bal-devnet branches") require.False(t, snap.KeepExecutionProofs, "keep execution proofs should be off by default") // Snapshot defaults @@ -210,6 +210,6 @@ func TestConfigSnapshotStability(t *testing.T) { require.True(t, snap.StateStream) require.True(t, snap.SnapProduceE2) require.True(t, snap.SnapProduceE3) - require.False(t, snap.ExperimentalBAL) + require.True(t, snap.ExperimentalBAL) require.False(t, snap.NoDownloader) } diff --git a/common/dbg/experiments.go b/common/dbg/experiments.go index d467d30bde5..c67d21116d4 100644 --- a/common/dbg/experiments.go +++ b/common/dbg/experiments.go @@ -77,7 +77,7 @@ var ( CaplinSyncedDataMangerDeadlockDetection = EnvBool("CAPLIN_SYNCED_DATA_MANAGER_DEADLOCK_DETECTION", false) - Exec3Parallel = EnvBool("EXEC3_PARALLEL", false) + Exec3Parallel = EnvBool("EXEC3_PARALLEL", true) numWorkers = runtime.NumCPU() / 2 Exec3Workers = EnvInt("EXEC3_WORKERS", numWorkers) ExecTerseLoggerLevel = EnvInt("EXEC_TERSE_LOGGER_LEVEL", int(log.LvlWarn)) diff --git a/execution/execmodule/execmoduletester/exec_module_tester.go b/execution/execmodule/execmoduletester/exec_module_tester.go index b77e1245700..ded2642fc04 100644 --- a/execution/execmodule/execmoduletester/exec_module_tester.go +++ b/execution/execmodule/execmoduletester/exec_module_tester.go @@ -366,7 +366,7 @@ func applyOptions(opts []Option) options { pruneMode: &defaultPruneMode, blockBufferSize: 128, chainConfig: chain.TestChainBerlinConfig, - experimentalBAL: false, + experimentalBAL: true, } for _, o := range opts { o(&opt) diff --git a/node/cli/flags.go b/node/cli/flags.go index 3172270887f..1ff30513393 100644 --- a/node/cli/flags.go +++ b/node/cli/flags.go @@ -118,7 +118,7 @@ var ( ExperimentalBALFlag = cli.BoolFlag{ Name: "experimental.bal", Usage: "generate block access list", - Value: false, + Value: true, } // Throttling Flags diff --git a/node/ethconfig/config.go b/node/ethconfig/config.go index 2268dcba340..470fa8c209a 100644 --- a/node/ethconfig/config.go +++ b/node/ethconfig/config.go @@ -114,7 +114,7 @@ var Defaults = Config{ FcuTimeout: 1 * time.Second, FcuBackgroundPrune: true, FcuBackgroundCommit: false, // to enable, we need to 1) have rawdb API go via execctx and 2) revive Coherent cache for rpcdaemon - ExperimentalBAL: false, + ExperimentalBAL: true, } const DefaultChainDBPageSize = 16 * datasize.KB From 43c403614388ab92b3aee16bcdee61b127e6fb9c Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Mon, 20 Apr 2026 01:17:52 +0000 Subject: [PATCH 08/31] execution: implement EIP-7981 --- execution/protocol/mdgas/intrinsic_gas.go | 79 +++++++++-- .../protocol/mdgas/intrinsic_gas_test.go | 126 ++++++++++++++++++ execution/protocol/params/protocol.go | 14 +- execution/protocol/txn_executor.go | 1 + .../tests/testutil/transaction_test_util.go | 1 + execution/types/aa_transaction.go | 1 + txnprovider/txpool/pool.go | 2 + 7 files changed, 207 insertions(+), 17 deletions(-) diff --git a/execution/protocol/mdgas/intrinsic_gas.go b/execution/protocol/mdgas/intrinsic_gas.go index 9fca28361bd..e406254d821 100644 --- a/execution/protocol/mdgas/intrinsic_gas.go +++ b/execution/protocol/mdgas/intrinsic_gas.go @@ -36,6 +36,7 @@ type IntrinsicGasCalcArgs struct { IsEIP2028 bool IsEIP3860 bool IsEIP7623 bool + IsEIP7981 bool IsEIP8037 bool IsAATxn bool } @@ -82,10 +83,10 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) result.RegularGas = params.TxGas } result.FloorGasCost = params.TxGas + nz := args.DataNonZeroLen // Bump the required gas by the amount of transactional data if dataLen > 0 { // Zero and non-zero bytes are priced differently - nz := args.DataNonZeroLen // Make sure we don't exceed uint64 for all data combinations nonZeroGas := params.TxDataNonZeroGasFrontier if args.IsEIP2028 { @@ -123,34 +124,88 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) return IntrinsicGasCalcResult{}, true } } + } + if args.AccessListLen > 0 { + product, overflow := math.SafeMul(args.AccessListLen, params.TxAccessListAddressGas) + if overflow { + return IntrinsicGasCalcResult{}, true + } + result.RegularGas, overflow = math.SafeAdd(result.RegularGas, product) + if overflow { + return IntrinsicGasCalcResult{}, true + } + + product, overflow = math.SafeMul(args.StorageKeysLen, params.TxAccessListStorageKeyGas) + if overflow { + return IntrinsicGasCalcResult{}, true + } + result.RegularGas, overflow = math.SafeAdd(result.RegularGas, product) + if overflow { + return IntrinsicGasCalcResult{}, true + } + } - if args.IsEIP7623 { - tokenLen := dataLen + 3*nz - dataGas, overflow := math.SafeMul(tokenLen, params.TxTotalCostFloorPerToken) + // Floor data gas cost. + // + // EIP-7981 supersedes EIP-7976 which supersedes EIP-7623. When EIP-7981 is active: + // - every calldata byte (zero or non-zero) counts as TxStandardTokensPerByte floor tokens + // - access list data (addresses + storage keys) also contributes floor tokens + // - access list data is always charged at floor rate in the intrinsic cost, so that + // access lists cannot be used to bypass the calldata floor pricing + if args.IsEIP7981 { + floorCalldataTokens, overflow := math.SafeMul(dataLen, params.TxStandardTokensPerByte) + if overflow { + return IntrinsicGasCalcResult{}, true + } + accessListBytes, overflow := math.SafeMul(args.AccessListLen, params.TxAccessListAddressBytes) + if overflow { + return IntrinsicGasCalcResult{}, true + } + storageKeyBytes, overflow := math.SafeMul(args.StorageKeysLen, params.TxAccessListStorageKeyBytes) + if overflow { + return IntrinsicGasCalcResult{}, true + } + accessListBytes, overflow = math.SafeAdd(accessListBytes, storageKeyBytes) + if overflow { + return IntrinsicGasCalcResult{}, true + } + floorAccessListTokens, overflow := math.SafeMul(accessListBytes, params.TxStandardTokensPerByte) + if overflow { + return IntrinsicGasCalcResult{}, true + } + + // Always charge the access list data cost in the standard intrinsic gas path so + // that access list data is charged at floor rate regardless of execution level. + if floorAccessListTokens > 0 { + accessListDataGas, overflow := math.SafeMul(floorAccessListTokens, params.TxTotalCostFloorPerTokenEIP7976) if overflow { return IntrinsicGasCalcResult{}, true } - result.FloorGasCost, overflow = math.SafeAdd(result.FloorGasCost, dataGas) + result.RegularGas, overflow = math.SafeAdd(result.RegularGas, accessListDataGas) if overflow { return IntrinsicGasCalcResult{}, true } } - } - if args.AccessListLen > 0 { - product, overflow := math.SafeMul(args.AccessListLen, params.TxAccessListAddressGas) + + totalFloorTokens, overflow := math.SafeAdd(floorCalldataTokens, floorAccessListTokens) if overflow { return IntrinsicGasCalcResult{}, true } - result.RegularGas, overflow = math.SafeAdd(result.RegularGas, product) + dataGas, overflow := math.SafeMul(totalFloorTokens, params.TxTotalCostFloorPerTokenEIP7976) if overflow { return IntrinsicGasCalcResult{}, true } - - product, overflow = math.SafeMul(args.StorageKeysLen, params.TxAccessListStorageKeyGas) + result.FloorGasCost, overflow = math.SafeAdd(result.FloorGasCost, dataGas) if overflow { return IntrinsicGasCalcResult{}, true } - result.RegularGas, overflow = math.SafeAdd(result.RegularGas, product) + } else if args.IsEIP7623 && dataLen > 0 { + tokenLen := dataLen + 3*nz + dataGas, overflow := math.SafeMul(tokenLen, params.TxTotalCostFloorPerToken) + if overflow { + return IntrinsicGasCalcResult{}, true + } + result.FloorGasCost, overflow = math.SafeAdd(result.FloorGasCost, dataGas) if overflow { return IntrinsicGasCalcResult{}, true } diff --git a/execution/protocol/mdgas/intrinsic_gas_test.go b/execution/protocol/mdgas/intrinsic_gas_test.go index ebc00e1f143..a2077418d50 100644 --- a/execution/protocol/mdgas/intrinsic_gas_test.go +++ b/execution/protocol/mdgas/intrinsic_gas_test.go @@ -108,3 +108,129 @@ func TestZeroDataIntrinsicGas(t *testing.T) { assert.Equal(params.TxGas, result.RegularGas) assert.Equal(params.TxGas, result.FloorGasCost) } + +// TestEIP7981IntrinsicGas covers EIP-7981 (Increase Access List Cost): +// access list data contributes to the floor calculation, and the access +// list data cost is always charged in the standard intrinsic gas path. +func TestEIP7981IntrinsicGas(t *testing.T) { + const ( + floorCostPerToken = 16 + tokensPerByte = 4 + addressBytes = 20 + storageKeyBytes = 32 + ) + cases := map[string]struct { + dataLen uint64 + dataNonZeroLen uint64 + accessListLen uint64 + storageKeysLen uint64 + expectedRegularGas uint64 + expectedFloorGasCost uint64 + }{ + "no data no access list": { + dataLen: 0, + dataNonZeroLen: 0, + accessListLen: 0, + storageKeysLen: 0, + expectedRegularGas: params.TxGas, + expectedFloorGasCost: params.TxGas, + }, + "only access list address": { + dataLen: 0, + dataNonZeroLen: 0, + accessListLen: 1, + storageKeysLen: 0, + // regular = 21000 + 2400 + (20*4)*16 = 21000 + 2400 + 1280 + expectedRegularGas: params.TxGas + params.TxAccessListAddressGas + addressBytes*tokensPerByte*floorCostPerToken, + // floor = 21000 + (20*4)*16 = 21000 + 1280 + expectedFloorGasCost: params.TxGas + addressBytes*tokensPerByte*floorCostPerToken, + }, + "access list with storage keys": { + dataLen: 0, + dataNonZeroLen: 0, + accessListLen: 1, + storageKeysLen: 2, + // access_list_bytes = 1*20 + 2*32 = 84; floor_tokens = 84*4 = 336; data gas = 336*16 = 5376 + // regular = 21000 + 2400 + 2*1900 + 5376 = 32576 + expectedRegularGas: params.TxGas + + params.TxAccessListAddressGas + + 2*params.TxAccessListStorageKeyGas + + (addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + expectedFloorGasCost: params.TxGas + (addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + }, + "calldata only all non-zero": { + dataLen: 32, + dataNonZeroLen: 32, + accessListLen: 0, + storageKeysLen: 0, + // regular = 21000 + 32*16 = 21512 + expectedRegularGas: params.TxGas + 32*params.TxDataNonZeroGasEIP2028, + // floor = 21000 + (32*4)*16 = 21000 + 2048 + expectedFloorGasCost: params.TxGas + 32*tokensPerByte*floorCostPerToken, + }, + "calldata only all zero bytes": { + dataLen: 32, + dataNonZeroLen: 0, + accessListLen: 0, + storageKeysLen: 0, + // regular = 21000 + 32*4 = 21128 + expectedRegularGas: params.TxGas + 32*params.TxDataZeroGas, + // EIP-7976: zero bytes also cost 4 tokens each for the floor => 21000 + (32*4)*16 = 23048 + expectedFloorGasCost: params.TxGas + 32*tokensPerByte*floorCostPerToken, + }, + "calldata and access list": { + dataLen: 32, + dataNonZeroLen: 32, + accessListLen: 1, + storageKeysLen: 2, + // access_list_bytes = 84, access list data gas = 84*4*16 = 5376 + // regular = 21000 + 32*16 + 2400 + 2*1900 + 5376 = 33088 + expectedRegularGas: params.TxGas + + 32*params.TxDataNonZeroGasEIP2028 + + params.TxAccessListAddressGas + + 2*params.TxAccessListStorageKeyGas + + (addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + // floor tokens = 32*4 + 84*4 = 128 + 336 = 464; floor = 21000 + 464*16 = 28424 + expectedFloorGasCost: params.TxGas + (32+addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + result, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, c.dataLen), + DataNonZeroLen: c.dataNonZeroLen, + AccessListLen: c.accessListLen, + StorageKeysLen: c.storageKeysLen, + IsEIP2: true, + IsEIP2028: true, + IsEIP7623: true, + IsEIP7981: true, + }) + assert.False(t, overflow) + assert.Equal(t, c.expectedRegularGas, result.RegularGas, "RegularGas mismatch") + assert.Equal(t, c.expectedFloorGasCost, result.FloorGasCost, "FloorGasCost mismatch") + }) + } +} + +// TestEIP7981NotActive verifies that when IsEIP7981 is false, the legacy +// EIP-7623 floor calculation is used (zero bytes count as 1 token, floor +// cost per token is 10, access list is not included in the floor). +func TestEIP7981NotActive(t *testing.T) { + result, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ + Data: make([]byte, 32), + DataNonZeroLen: 32, + AccessListLen: 1, + StorageKeysLen: 2, + IsEIP2: true, + IsEIP2028: true, + IsEIP7623: true, + IsEIP7981: false, + }) + assert.False(t, overflow) + // Regular: 21000 + 32*16 + 2400 + 2*1900 = 27712 (no access list data floor charge) + assert.Equal(t, params.TxGas+32*params.TxDataNonZeroGasEIP2028+params.TxAccessListAddressGas+2*params.TxAccessListStorageKeyGas, result.RegularGas) + // Floor (EIP-7623): 21000 + (32 + 3*32)*10 = 22280 (access list not in floor) + assert.Equal(t, params.TxGas+(32+3*32)*params.TxTotalCostFloorPerToken, result.FloorGasCost) +} diff --git a/execution/protocol/params/protocol.go b/execution/protocol/params/protocol.go index ab44e037ec5..3042dfebba6 100644 --- a/execution/protocol/params/protocol.go +++ b/execution/protocol/params/protocol.go @@ -94,11 +94,15 @@ const ( SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. - TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) - TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list - TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list - TxTotalCostFloorPerToken uint64 = 10 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7623) + TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list + TxTotalCostFloorPerToken uint64 = 10 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7623) + TxTotalCostFloorPerTokenEIP7976 uint64 = 16 // Per token of calldata in a transaction, as a minimum the txn must pay (EIP-7976, reused by EIP-7981) + TxAccessListAddressBytes uint64 = 20 // Byte length of an access list address (EIP-7981) + TxAccessListStorageKeyBytes uint64 = 32 // Byte length of an access list storage key (EIP-7981) + TxStandardTokensPerByte uint64 = 4 // Tokens per byte for EIP-7976 / EIP-7981 floor calculation // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index 6783721cc22..af0b80824aa 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -839,6 +839,7 @@ func (st *TxnExecutor) calcIntrinsicGas(contractCreation bool, auths []types.Aut IsEIP2028: rules.IsIstanbul, IsEIP3860: vmConfig.HasEip3860(rules), IsEIP7623: rules.IsPrague, + IsEIP7981: rules.IsAmsterdam, IsEIP8037: rules.IsAmsterdam, }) } diff --git a/execution/tests/testutil/transaction_test_util.go b/execution/tests/testutil/transaction_test_util.go index 76b8c587e34..0df827c6332 100644 --- a/execution/tests/testutil/transaction_test_util.go +++ b/execution/tests/testutil/transaction_test_util.go @@ -90,6 +90,7 @@ func (tt *TransactionTest) Run(chainID *big.Int) error { IsEIP2028: rules.IsIstanbul, IsEIP3860: rules.IsShanghai, IsEIP7623: rules.IsPrague, + IsEIP7981: rules.IsAmsterdam, IsEIP8037: rules.IsAmsterdam, }) requiredGas := intrinsicGasResult.RegularGas diff --git a/execution/types/aa_transaction.go b/execution/types/aa_transaction.go index a3664837a1d..f58914aaaeb 100644 --- a/execution/types/aa_transaction.go +++ b/execution/types/aa_transaction.go @@ -501,6 +501,7 @@ func (tx *AccountAbstractionTransaction) PreTransactionGasCost(rules *chain.Rule IsEIP2028: rules.IsIstanbul, IsEIP3860: hasEIP3860, IsEIP7623: rules.IsPrague, + IsEIP7981: rules.IsAmsterdam, IsEIP8037: rules.IsAmsterdam, IsAATxn: true, }) diff --git a/txnprovider/txpool/pool.go b/txnprovider/txpool/pool.go index 806a20ab16b..0d7a53d0306 100644 --- a/txnprovider/txpool/pool.go +++ b/txnprovider/txpool/pool.go @@ -793,6 +793,7 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf uint64, IsEIP2028: true, IsEIP3860: isEIP3860, IsEIP7623: isEIP7623, + IsEIP7981: p.isAmsterdam(), IsEIP8037: p.isAmsterdam(), IsAATxn: isAATxn, }) @@ -982,6 +983,7 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache IsEIP2028: true, IsEIP3860: isEIP3860, IsEIP7623: isPrague, + IsEIP7981: p.isAmsterdam(), IsEIP8037: p.isAmsterdam(), IsAATxn: isAATxn, }) From 9ebc449cab3f182c27bae75fba79565afd013c44 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:37:19 +0000 Subject: [PATCH 09/31] address reviews --- execution/protocol/mdgas/intrinsic_gas.go | 11 +++++++++-- execution/protocol/mdgas/intrinsic_gas_test.go | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/execution/protocol/mdgas/intrinsic_gas.go b/execution/protocol/mdgas/intrinsic_gas.go index 61b4e1bbe26..542bd83aed5 100644 --- a/execution/protocol/mdgas/intrinsic_gas.go +++ b/execution/protocol/mdgas/intrinsic_gas.go @@ -128,10 +128,10 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) if args.IsEIP7623 { var tokenLen uint64 var costPerToken uint64 - var overflow bool if args.IsEIP7976 { // EIP-7976: floor_tokens = total_bytes * 4, cost_per_token = 16 // => 64 gas per byte (both zero and non-zero) + var overflow bool tokenLen, overflow = math.SafeMul(dataLen, 4) if overflow { return IntrinsicGasCalcResult{}, true @@ -139,7 +139,14 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) costPerToken = params.TxTotalCostFloorPerTokenEIP7976 } else { // EIP-7623: tokens = zero_bytes + 4*nonzero_bytes = dataLen + 3*nz - tokenLen = dataLen + 3*nz + nzTokens, overflow := math.SafeMul(3, nz) + if overflow { + return IntrinsicGasCalcResult{}, true + } + tokenLen, overflow = math.SafeAdd(dataLen, nzTokens) + if overflow { + return IntrinsicGasCalcResult{}, true + } costPerToken = params.TxTotalCostFloorPerToken } dataGas, overflow := math.SafeMul(tokenLen, costPerToken) diff --git a/execution/protocol/mdgas/intrinsic_gas_test.go b/execution/protocol/mdgas/intrinsic_gas_test.go index a4ff13baa0e..1db71cf22e5 100644 --- a/execution/protocol/mdgas/intrinsic_gas_test.go +++ b/execution/protocol/mdgas/intrinsic_gas_test.go @@ -170,8 +170,8 @@ func TestEIP7976FloorCost(t *testing.T) { func TestEIP7976VsEIP7623Floor(t *testing.T) { // Compare EIP-7976 vs EIP-7623 floor costs for the same data. - // EIP-7976 should always be >= EIP-7623 for zero bytes, - // and equal or different for non-zero bytes. + // EIP-7976 is strictly greater than EIP-7623 for any non-empty calldata + // (64 > 10 for zero bytes, 64 > 40 for non-zero bytes). assert := assert.New(t) // 32 non-zero bytes: From cd78c84e9eeb1b43f95ed97637dfc0ad2fb5b0d8 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:59:15 +0200 Subject: [PATCH 10/31] update skips --- execution/tests/eest_devnet/block_test.go | 86 +++++------------------ 1 file changed, 17 insertions(+), 69 deletions(-) diff --git a/execution/tests/eest_devnet/block_test.go b/execution/tests/eest_devnet/block_test.go index 879923a3c5e..71db81f237c 100644 --- a/execution/tests/eest_devnet/block_test.go +++ b/execution/tests/eest_devnet/block_test.go @@ -66,20 +66,17 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_insufficient_balance_no_log.json`) // block=1, gas used by execution: 131488, in header: 32226 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_out_of_gas_no_log.json`) // block=1, receiptHash mismatch: 009438deb1de46992abb88fe0ae9a0ddac9f51e271e187f90e8ee24ba2cb5ad0 != dd8803e13b8cb71811d62735df0b9b37d8c2526eae428f0c655cc8a4c6ff3126, headerNum=1, 64d6033a2a26d14031c313d27413f94c127a47314a50ed3a9150b7091dab3be4 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/failed_create_with_value_no_log.json`) // block=1, receiptHash mismatch: 0287aff45fdc09786733241c6048608415aa4f99b6e873b72be6cd8d861ef799 != 6a84ba66d7c74c68e9eee3ae1f5bdff1983a21b6bb5d4fcac2194132e0c4023a, headerNum=1, f7943b7f9af73920eb176bf0934826a430f25675eecb396660d2f5dbced1d997 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/transfer_with_all_tx_types.json`) // block=1, gas used by execution: 22000, in header: 27400 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/multi_transaction_gas_accounting.json`) // block=1, gas used by execution: 92090, in header: 92114 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/transfer_with_all_tx_types.json`) // block=1, gas used by execution: 40000, in header: 56128 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/multiple_refund_types_in_one_tx.json`) // block=1, gas used by execution: 270020, in header: 1584900 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/simple_gas_accounting.json`) // block=1, gas used by execution: 270020, in header: 1584900 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/varying_calldata_costs.json`) // block=1, gas used by execution: 31062, in header: 158490 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/varying_calldata_costs.json`) // block=1, gas used by execution: 31240, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_account_listed_but_untouched.json`) // block=1, gas used by execution: 25300, in header: 28628 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_slot_listed_and_unlisted_reads.json`) // block=1, gas used by execution: 27506, in header: 30834 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_slot_listed_and_unlisted_writes.json`) // block=1, receiptHash mismatch: 4445caea7a2485f5e2b50b1c31521f6425b9644ef51180f55d59d70e8bfa5e0f != 7433756f583f4418305ba1bbdda7a9eaf89f1efdac632ed914e53919083aa8ce, headerNum=1, 11ec9293c65bd3e0fe4d23c088d7fcf1deddadd241312b4fdbc85991fac97f9f bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_slot_listed_but_untouched.json`) // block=1, gas used by execution: 25309, in header: 28637 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_aborted_storage_access.json`) // block=1, receiptHash mismatch: 0574ca632811062d8709db6085aef9953a58cc20c1f2a9f2fd58973ee9fc43c5 != 3436c842cadfa64946a5dee36dd6ceaab7cbaab05b7ddcb3e8069b72bcf8620a, headerNum=1, 1efe1b367f53fbddf9e77735a144532932bd5aec9d496bccf4adc5ba0c4fe78f bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_all_transaction_types.json`) // block=1, gas used by execution: 214842, in header: 346330 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_cross_tx_storage_revert_to_zero.json`) // block=1, receiptHash mismatch: a6ac208e3a45183cb6c40f54919d814396fa128f0e07423325fedc58e67812fd != d856722f7ccb7e42263ba64d8b0ced05afff903fba0a0128425744f0de7dbf9d, headerNum=1, ebcd267ab1838b29e17d27dfaff171f57810d45ad5605f005c73ea8148a4cda8 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_precompile_funded.json`) // block=1, gas used by execution: 24512, in header: 29192 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7002/bal_7002_request_invalid.json`) // block=1, gas used by execution: 23584, in header: 24648 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7002/bal_7002_request_invalid.json`) // block=1, gas used by execution: 150272, in header: 56234 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_clear.json`) // block=1, gas used by execution: 57000, in header: 316980 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_create.json`) // block=1, gas used by execution: 28500, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_update.json`) // block=1, gas used by execution: 57000, in header: 316980 @@ -88,40 +85,28 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_null_address_delegation_no_code_change.json`) // block=1, gas used by execution: 28500, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_selfdestruct_to_7702_delegation.json`) // block=1, gas used by execution: 59724, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_withdrawal_to_7702_delegation.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_7702_delegation_and_oog.json`) // block=1 (hash=0xb43d86b6cd3e1d8b8f7f4b684987fa47a4a669edcc6143787a9b72c62b53aff2): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-1471576661/bal + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_7702_delegation_and_oog.json`) // block=1 (hash=0xb43d86b6cd3e1d8b8f7f4b684987fa47a4a669edcc6143787a9b72c62b53aff2): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-1584648681/bal bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23521, in header: 24800 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_with_value_in_static_context.json`) // block=1, gas used by execution: 1036027, in header: 1037307 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_callcode_7702_delegation_and_oog.json`) // block=1 (hash=0xd7ec8d3e0ee01f1a8e0a0e40b96cf75648113f08acc5879ec5b8967cba48eda4): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-3734185478/bal + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_callcode_7702_delegation_and_oog.json`) // block=1 (hash=0xd7ec8d3e0ee01f1a8e0a0e40b96cf75648113f08acc5879ec5b8967cba48eda4): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-944926237/bal bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_callcode_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23521, in header: 24800 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create2_collision.json`) // block=1, receiptHash mismatch: 58c109854ad17a7e6cc36919cc07464496eba4562546763c5719a54dbcf83572 != c0f65f206dc904c838752c6d10e9e70f72efefaefb9f01c5793a730fc4d0ef43, headerNum=1, c650b7b61e4cee14205def343f64864259bcbeb9b127686d448f73dd086cd04b bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_and_oog.json`) // block=1, gas used by execution: 131488, in header: 30023 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_early_failure.json`) // block=1, gas used by execution: 131488, in header: 35026 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_oog_code_deposit.json`) // block=1, receiptHash mismatch: 432aa57d091596e6f080cb880aa2265c360922292588ee56480677ce29c931a1 != fd591027210a4da480975eab0be900db75960133ac29c6e4f479df76fe730315, headerNum=1, f0e3464870d4f197c97ef93cb229adb7fe0898b9333aaeb9da79f3095d8b38e8 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_selfdestruct_to_self_with_call.json`) // block=1, gas used by execution: 244192, in header: 75136 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_delegatecall_7702_delegation_and_oog.json`) // block=1 (hash=0xaebf8f0426fa03d66ccc61a55b6bdd38c00126d3f9e7c2a2270382de77ca22ec): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-498116264/bal + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_delegatecall_7702_delegation_and_oog.json`) // block=1 (hash=0xaebf8f0426fa03d66ccc61a55b6bdd38c00126d3f9e7c2a2270382de77ca22ec): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-4136835721/bal bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_delegatecall_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23518, in header: 24797 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_selfdestruct_in_static_context.json`) // block=1, gas used by execution: 1036027, in header: 1037307 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_staticcall_7702_delegation_and_oog.json`) // block=1 (hash=0x8ef752f47ac4f89d34f8bfa2155254ef393d74b486d3f3866312f183fe9afb29): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-1337124827/bal + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_staticcall_7702_delegation_and_oog.json`) // block=1 (hash=0x8ef752f47ac4f89d34f8bfa2155254ef393d74b486d3f3866312f183fe9afb29): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-2349312826/bal bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_staticcall_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23518, in header: 24797 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_code_size/max_code_size_via_create.json`) // block=1, receiptHash mismatch: 764a45a52f65e30545bc1acec647b68bef0de17812d850a420716c19b6c19f37 != f2baac99015cfaaf4245fccad11e911bf1bba7f51678131b54269bf903cb6316, headerNum=1, 68ec0f21c9fdd7438f527c256bed133b73eb921c5db72957fa0516467b688547 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_initcode_size/max_initcode_size.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_initcode_size/max_initcode_size_gas_metering.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_initcode_size/max_initcode_size_via_create.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/exact_threshold_boundary.json`) // block=1, gas used by execution: 21040, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/floor_cost_not_reduced_by_refunds.json`) // block=1, gas used by execution: 61000, in header: 85000 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/maximum_calldata_size.json`) // block=1, gas used by execution: 6257840, in header: 9999944 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/memory_expansion_with_calldata.json`) // block=1, gas used by execution: 430600, in header: 676360 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/refund_calculated_from_execution_not_floor.json`) // block=1, gas used by execution: 41000, in header: 53000 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/token_calculation_verification.json`) // block=1, gas used by execution: 22750, in header: 27400 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/eip_mainnet/eip_7976.json`) // block=1, gas used by execution: 21010, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/execution_gas/full_gas_consumption.json`) // block=1, gas used by execution: 201000, in header: 309000 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/execution_gas/gas_consumption_below_data_floor.json`) // block=1, gas used by execution: 21063, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/refunds/gas_refunds_from_data_floor.json`) // block=1, gas used by execution: 215694, in header: 242696 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_0.json`) // block=1, gas used by execution: 21010, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_1_type_2.json`) // block=1, gas used by execution: 380680, in header: 814088 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_3.json`) // block=1, gas used by execution: 380680, in header: 814088 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_4.json`) // block=1, gas used by execution: 1763920, in header: 3027272 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/access_list_floor_cost_with_calldata.json`) // block=1, gas used by execution: 26900, in header: 30728 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/exact_threshold_boundary.json`) // block=1, gas used by execution: 25928, in header: 27208 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/eip_mainnet/eip_7976.json`) // block=1, gas used by execution: 31176, in header: 34504 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_1_type_2.json`) // block=1, gas used by execution: 596488, in header: 814088 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_3.json`) // block=1, gas used by execution: 596488, in header: 814088 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_4.json`) // block=1, gas used by execution: 2809672, in header: 3027272 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/access_list_floor_cost_with_calldata.json`) // block=1, gas used by execution: 27400, in header: 30728 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/access_list_token_calculation.json`) // block=1, gas used by execution: 25300, in header: 28628 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/duplicate_access_list_entries.json`) // block=1, gas used by execution: 29600, in header: 36256 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/large_access_list_cost.json`) // block=1, gas used by execution: 80500, in header: 138100 @@ -144,10 +129,6 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_value_to_self_destructed_same_tx_account.json`) // block=1, gas used by execution: 169056, in header: 46874 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_zero_value_to_self_destructed_same_tx_account.json`) // block=1, gas used by execution: 131488, in header: 35173 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/create_insufficient_balance_returns_reservoir.json`) // block=1, gas used by execution: 169056, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_applied_to_sender_refund.json`) // block=1, gas used by execution: 61960, in header: 86536 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_exceeding_tx_gas_limit_cap.json`) // block=1, gas used by execution: 10493600, in header: 16777160 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_higher_than_execution_with_state_ops.json`) // block=1, gas used by execution: 61960, in header: 86536 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_independent_of_state_gas.json`) // block=1, gas used by execution: 41480, in header: 53768 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/code_deposit_halt_discards_initcode_state_gas.json`) // block=1, receiptHash mismatch: 6109c57d8983f520f4b510024a1760aff8d920bb293bbc00c30abdac956b3b2f != c7cd454c1e2d8f590ccd8205e653d2649edb81e2ee1259782fb3af690d5648d8, headerNum=1, 89a4abe5fd3c9d1b72b7dc5e1a8aba9fd3164ec844156307f23ceac5b92181a5 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/code_deposit_oog_preserves_parent_reservoir.json`) // block=1, receiptHash mismatch: 87eb1d5efeccbf21568dc8ba422e793d3dc3ca7bea2c064adcd6d4bf89c1f636 != 13caceefb2a53e479514f660e5d50b365ead011387defffccca23e4d8d03c5f4, headerNum=1, 59aa99f406a68008b33cc2807b9bd2988a55798b270b671b549c2923b703baad bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create2_address_collision.json`) // block=1, receiptHash mismatch: ed982053122348c512e18c1ac8827d4e10ef91c2705616cd872dc2ad2a0b03dd != 7637ffd9bbf631be43879032b61a2d3ece5335e64500422749a7ea9001482b68, headerNum=1, c148d12a917d4fc0c5dafc6ffa352ee44d216546824653e3b697b3ac7dbf9ac1 @@ -162,15 +143,11 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/failed_create_header_gas_used.json`) // block=1, receiptHash mismatch: 9adb08ce7ee8cdea800360deb4ac97ab74ab228a85c1c3756ad1b8c6b675adf3 != 769baff2fb1b33f057f8234087ba691afc2a7e92d81d7dc023a960fc22378d16, headerNum=1, 5c835fc83a5400f48feeb928f38e7f3dc05966214a9dbeee905b3bc0e6089fd6 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/inner_create_fail_refunds_in_creation_tx.json`) // block=1, gas used by execution: 525952, in header: 131488 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/inner_create_succeeds_code_deposit_state_gas.json`) // block=1, gas used by execution: 264150, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/max_initcode_size_gas_metering_via_create.json`) // block=1, gas used by execution: 676630, in header: 4215304 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/nested_create_code_deposit_cannot_borrow_parent_gas.json`) // block=1, gas used by execution: 131488, in header: 30620 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/nested_create_fail_parent_revert_state_gas.json`) // block=1, receiptHash mismatch: 7bcf94219e25ac0732ab6b16e86421b8ca83921986485c99782a0be5579d3a87 != c55cbaff662c04ece0a9c550a57a05b480590f30131afd89fab8b99a29f685a5, headerNum=1, ea6f529ce05e9a22fe8b59ace8401967a32ff6bfaf0f80a9a3a044b3cdd1cc29 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/oversized_initcode_opcode_no_state_gas.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/oversized_initcode_tx_no_state_gas.json`) // block=1, gas used by execution: 676630, in header: 4215304 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/selfdestruct_in_create_tx_initcode.json`) // block=1, gas used by execution: 262976, in header: 131488 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/auth_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 102138, in header: 233626 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/call_new_account_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/calldata_floor_enforced_with_state_gas.json`) // block=1, gas used by execution: 25000, in header: 27400 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/cpsb_underflow_boundary.json`) // block=1, gas used by execution: 37568, in header: 26006 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/create_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/pricing_at_various_gas_limits.json`) // block=1, gas used by execution: 37568, in header: 381632 @@ -230,20 +207,17 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/acl/repeated_address_acl.json`) // block=1, receiptHash mismatch: fc6a9666511f1769ef9170ad67316857f70034c974f82e8bb29d8718785410f9 != 925c49d754f5ae98a0f4b9809b1b6374183eb23049041353208a309fd19cbf87, headerNum=1, 65b22b0f8a3bb6791b7a17829222c06cf3c3cea1120923c461f2a50e64d01d41 bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/acl/transaction_intrinsic_gas_cost.json`) // block=1, gas used by execution: 29600, in header: 36256 bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/tx_intrinsic_gas/tx_intrinsic_gas.json`) // block=1, gas used by execution: 134676, in header: 231040 - bt.SkipLoad(`^for_amsterdam/byzantium/eip196_ec_add_mul/ecadd/invalid.json`) // block=1, gas used by execution: 26609, in header: 29192 bt.SkipLoad(`^for_amsterdam/cancun/create/create_oog_from_eoa_refunds/create_oog_from_eoa_refunds.json`) // block=1, receiptHash mismatch: ba9991e573821b0c234e361e68d2fe4b0fc940f8380d61fc18ee6ceb963b7094 != 91e4445a3ed8b70b41017281d14d817b16413ae3d96c8afaabd680ec95ff42d9, headerNum=1, 0d4c5d4b752c49e59f005ae61edeb5fadca9a7609e9d100e14097e7f492b1ff5 bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_create_contexts/tstore_rollback_on_failed_create.json`) // block=1, gas used by execution: 16776219, in header: 16776220 bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_selfdestruct/reentrant_selfdestructing_call.json`) // block=1, gas used by execution: 335764, in header: 112704 bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/beacon_root_contract_timestamps.json`) // block=1, receiptHash mismatch: a08468bbc451b952113931bb879542c55e3d7b2ccf4deb042d0b94f8c57f86df != e7ccd1a812b8e285ebd71350997600f2cb445cbc4418c19232a5392071516914, headerNum=1, 310f42900fe908a6ae29313e40fdd04368834e2dabb4431167929c4e7c718344 bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/beacon_root_equal_to_timestamp.json`) // block=1, receiptHash mismatch: a08468bbc451b952113931bb879542c55e3d7b2ccf4deb042d0b94f8c57f86df != e7ccd1a812b8e285ebd71350997600f2cb445cbc4418c19232a5392071516914, headerNum=1, d7f435eb5a5dc52f308311a30cd9db80eda1320c56953dbafc55bb5c25890950 - bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/calldata_lengths.json`) // block=1, gas used by execution: 36819, in header: 86536 bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/tx_to_beacon_root_contract.json`) // block=1, gas used by execution: 27660, in header: 33036 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blob_txs/blob_gas_subtraction_tx.json`) // block=1, receiptHash mismatch: 6a54bbdc096ab24565de7bf4759b6f7be19d810de95cf864ee13dd4b708adde5 != 5dff71537f505e5214e9fa0e1ad143f08fe119b94a30f008761c18c57ffa621e, headerNum=1, 1385b20a9eb8dd45b665fea39481e78ea47dbc1d81acb82673d9288ad7910d71 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blob_txs/sufficient_balance_blob_tx.json`) // block=1, gas used by execution: 27200, in header: 32576 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blob_txs/sufficient_balance_blob_tx_pre_fund_tx.json`) // block=1, gas used by execution: 48200, in header: 53576 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blobhash_opcode/blobhash_invalid_blob_index.json`) // block=1, gas used by execution: 413248, in header: 88332 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blobhash_opcode/blobhash_scenarios.json`) // block=1, gas used by execution: 450816, in header: 225408 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/point_evaluation_precompile/invalid_inputs.json`) // block=1, receiptHash mismatch: 80103092893bf0d37059859da6b918e6ab07d3b2cfba5ae88fd16923579da4b8 != 9d85dc4a419a68fa73ebdfd5562cd949bfaa2d201e39a6af221e303cbe0566bc, headerNum=1, 49791b8a87b0af17867656adad724e157f4ddb42820fcc07a63a7f853e19f949 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/point_evaluation_precompile/tx_entry_point.json`) // block=1, gas used by execution: 94148, in header: 105668 bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_huge_memory_expansion.json`) // block=1, receiptHash mismatch: 56fe7a0c09434e88468b961fffa5d328dd45d99b0a8d254178c45b1c33ab7a12 != 98ff002f51d977b4e85bd9a688d348d42c26f3b2f31fbfe9f005555adf3e5027, headerNum=1, f5309ec689b572ac095a551de1ab894b6782605b03012a48469f0f961cdd3e94 bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_memory_expansion.json`) // block=1, gas used by execution: 47635, in header: 59154 @@ -272,43 +246,18 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/frontier/create/create_suicide_during_init/create_suicide_during_transaction_create.json`) // block=1, gas used by execution: 169056, in header: 46953 bt.SkipLoad(`^for_amsterdam/frontier/create/create_suicide_store/create_suicide_store.json`) // block=1, gas used by execution: 413248, in header: 112704 bt.SkipLoad(`^for_amsterdam/frontier/opcodes/all_opcodes/cover_revert.json`) // block=1, gas used by execution: 169056, in header: 131488 - bt.SkipLoad(`^for_amsterdam/frontier/precompiles/ripemd/precompiles.json`) // block=1, gas used by execution: 421000, in header: 661000 bt.SkipLoad(`^for_amsterdam/frontier/scenarios/scenarios/scenarios.json`) // block=21, gas used by execution: 131488, in header: 50495 bt.SkipLoad(`^for_amsterdam/istanbul/eip1344_chainid/chainid/chainid.json`) // block=1, gas used by execution: 45005, in header: 61133 - bt.SkipLoad(`^for_amsterdam/osaka/eip7823_modexp_upper_bounds/modexp_upper_bounds/modexp_upper_bounds.json`) // block=1, gas used by execution: 150272, in header: 158280 - bt.SkipLoad(`^for_amsterdam/osaka/eip7825_transaction_gas_limit_cap/tx_gas_limit/tx_gas_limit_cap_contract_creation.json`) // block=1, gas used by execution: 1331720, in header: 2118152 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/modexp_gas_usage_contract_wrapper.json`) // block=1, gas used by execution: 28521, in header: 32520 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/modexp_legacy_oversized_inputs.json`) // block=1, gas used by execution: 27880, in header: 31240 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/modexp_used_in_transaction_entry_points.json`) // block=1, gas used by execution: 25350, in header: 32520 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/vectors_from_eip.json`) // block=1, gas used by execution: 150272, in header: 158280 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_at_rlp_limit_with_logs.json`) // block=1, gas used by execution: 84572150, in header: 537292760 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_at_rlp_limit_with_withdrawals.json`) // block=1, gas used by execution: 84551780, in header: 537275792 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_at_rlp_size_limit_boundary.json`) // block=1, gas used by execution: 84552280, in header: 537278992 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_rlp_size_at_limit_with_all_typed_transactions.json`) // block=1, gas used by execution: 84552420, in header: 537279888 - bt.SkipLoad(`^for_amsterdam/osaka/eip7951_p256verify_precompiles/p256verify/gas.json`) // block=1, gas used by execution: 28105, in header: 31240 - bt.SkipLoad(`^for_amsterdam/osaka/eip7951_p256verify_precompiles/p256verify/precompile_as_tx_entry_point.json`) // block=1, gas used by execution: 30436, in header: 31240 + bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_rlp_size_at_limit_with_all_typed_transactions.json`) // block=1, gas used by execution: 537261968, in header: 537278096 bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/collision_selfdestruct/selfdestruct_after_create2_collision.json`) // block=1, gas used by execution: 499019, in header: 409422 bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/initcollision/init_collision_create_opcode.json`) // block=1, receiptHash mismatch: 524db87016bef6d465f58a76b2d203a5aa7530ed58655fc1a222fc976da98830 != 0ef8df661f25e664b1912098fbf14882312c23e5318ea1af9948df4ad71304eb, headerNum=1, 441aaedd3994f8ef204e166eefeb7cfc63b2250c0423d68737a120ebba7f117e bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/revert_in_create/create2_collision_storage.json`) // block=1, receiptHash mismatch: c5beba07298d35464a539c2062ecf900c130d2400e66393b762a20377e6af894 != e019c41e16c55b80810e927d1bba60fc149d3da257bd5a4f83260142b1debc8f, headerNum=1, 2c115de7d2427ab2aac6d6d93890448d389878a11e1cb6dec62a5c7ac91e75ab bt.SkipLoad(`^for_amsterdam/paris/security/selfdestruct_balance_bug/tx_selfdestruct_balance_bug.json`) // block=1, gas used by execution: 266498, in header: 128408 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1add/gas.json`) // block=1, gas used by execution: 26985, in header: 37384 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1add/invalid.json`) // block=1, gas used by execution: 29290, in header: 37384 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1msm/call_types.json`) // block=1, gas used by execution: 1111968, in header: 1331720 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1msm/valid.json`) // block=1, gas used by execution: 751712, in header: 1045000 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1mul/invalid.json`) // block=1, gas used by execution: 25941, in header: 27144 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2add/gas.json`) // block=1, gas used by execution: 28282, in header: 53768 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2add/invalid.json`) // block=1, gas used by execution: 37640, in header: 53768 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2msm/valid.json`) // block=1, gas used by execution: 1382442, in header: 1864200 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2mul/invalid.json`) // block=1, gas used by execution: 26209, in header: 31240 - bt.SkipLoad(`^for_amsterdam/prague/eip6110_deposits/deposits/deposit.json`) // block=1, gas used by execution: 11619209, in header: 11656200 - bt.SkipLoad(`^for_amsterdam/prague/eip7002_el_triggerable_withdrawals/modified_withdrawal_contract/extra_withdrawals.json`) // block=1, gas used by execution: 329850, in header: 368760 + bt.SkipLoad(`^for_amsterdam/prague/eip6110_deposits/deposits/deposit.json`) // block=1, gas used by execution: 112704, in header: 95374 bt.SkipLoad(`^for_amsterdam/prague/eip7002_el_triggerable_withdrawals/withdrawal_requests/withdrawal_requests.json`) // block=1, gas used by execution: 225408, in header: 150272 bt.SkipLoad(`^for_amsterdam/prague/eip7251_consolidations/consolidations/consolidation_requests.json`) // block=1, gas used by execution: 262976, in header: 187840 - bt.SkipLoad(`^for_amsterdam/prague/eip7251_consolidations/modified_consolidation_contract/extra_consolidations.json`) // block=1, gas used by execution: 22020, in header: 27144 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/execution_gas/gas_consumption_below_data_floor.json`) // block=1, gas used by execution: 21063, in header: 21064 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_0.json`) // block=1, gas used by execution: 21010, in header: 21064 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_3.json`) // block=1, gas used by execution: 380620, in header: 814088 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_4.json`) // block=1, gas used by execution: 1763860, in header: 3027272 + bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_3.json`) // block=1, gas used by execution: 596488, in header: 814088 + bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_4.json`) // block=1, gas used by execution: 2809672, in header: 3027272 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/gas/account_warming.json`) // block=1, gas used by execution: 185492, in header: 316980 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/creating_delegation_designation_contract.json`) // block=1, receiptHash mismatch: eb0a2410285068047569b15f5785dacab88b92c4551f532189e41037162268dc != f5610820eb298919377f6d68f2eb097e952382f07a765552fb25d2a0b8bdad59, headerNum=1, c839439445443752f8b49facf7f0eff1f457461e8346d022e9b5683713ffd258 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/delegation_clearing.json`) // block=1, gas used by execution: 102138, in header: 233626 @@ -349,7 +298,6 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_to_precompile.json`) // block=1, gas used by execution: 64570, in header: 196058 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_to_static_reentry.json`) // block=1, gas used by execution: 140147, in header: 233626 bt.SkipLoad(`^for_amsterdam/shanghai/eip3855_push0/push0/push0_contracts.json`) // block=1, receiptHash mismatch: 777f1c1c378807634128348e4f0eeca6a0e7f516ea411690ca04266323f671a4 != 2f13c48591f063e30a792a180116e5ef2611efc62565ec81f9cbe853e23bc631, headerNum=1, a89980a26041d5d56ba7192f619f4dc13d8bed40c41fff9f8be25b15b5a207eb - bt.SkipLoad(`^for_amsterdam/shanghai/eip3860_initcode/initcode/contract_creating_tx.json`) // block=1, gas used by execution: 1986990, in header: 3166728 bt.SkipLoad(`^for_amsterdam/shanghai/eip3860_initcode/initcode/create2_oversized_initcode_with_insufficient_balance.json`) // block=1, gas used by execution: 169056, in header: 37568 bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/initcode_selfdestruct_to_self.json`) // block=1, gas used by execution: 131488, in header: 35040 bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_state_access_boundary.json`) // block=1, gas used by execution: 31024, in header: 32304 From ee3c6fe76c7e81cbf1d0b9e5466022c725e7e392 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Thu, 23 Apr 2026 16:30:59 +0200 Subject: [PATCH 11/31] update skips --- execution/tests/eest_devnet/block_test.go | 117 ++-------------------- 1 file changed, 11 insertions(+), 106 deletions(-) diff --git a/execution/tests/eest_devnet/block_test.go b/execution/tests/eest_devnet/block_test.go index 879923a3c5e..754a0541a0a 100644 --- a/execution/tests/eest_devnet/block_test.go +++ b/execution/tests/eest_devnet/block_test.go @@ -66,20 +66,13 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_insufficient_balance_no_log.json`) // block=1, gas used by execution: 131488, in header: 32226 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_out_of_gas_no_log.json`) // block=1, receiptHash mismatch: 009438deb1de46992abb88fe0ae9a0ddac9f51e271e187f90e8ee24ba2cb5ad0 != dd8803e13b8cb71811d62735df0b9b37d8c2526eae428f0c655cc8a4c6ff3126, headerNum=1, 64d6033a2a26d14031c313d27413f94c127a47314a50ed3a9150b7091dab3be4 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/failed_create_with_value_no_log.json`) // block=1, receiptHash mismatch: 0287aff45fdc09786733241c6048608415aa4f99b6e873b72be6cd8d861ef799 != 6a84ba66d7c74c68e9eee3ae1f5bdff1983a21b6bb5d4fcac2194132e0c4023a, headerNum=1, f7943b7f9af73920eb176bf0934826a430f25675eecb396660d2f5dbced1d997 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/transfer_with_all_tx_types.json`) // block=1, gas used by execution: 22000, in header: 27400 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/multi_transaction_gas_accounting.json`) // block=1, gas used by execution: 92090, in header: 92114 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/transfer_with_all_tx_types.json`) // block=1, gas used by execution: 63900, in header: 316980 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/multiple_refund_types_in_one_tx.json`) // block=1, gas used by execution: 270020, in header: 1584900 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/simple_gas_accounting.json`) // block=1, gas used by execution: 270020, in header: 1584900 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/varying_calldata_costs.json`) // block=1, gas used by execution: 31062, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_account_listed_but_untouched.json`) // block=1, gas used by execution: 25300, in header: 28628 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_slot_listed_and_unlisted_reads.json`) // block=1, gas used by execution: 27506, in header: 30834 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_slot_listed_and_unlisted_writes.json`) // block=1, receiptHash mismatch: 4445caea7a2485f5e2b50b1c31521f6425b9644ef51180f55d59d70e8bfa5e0f != 7433756f583f4418305ba1bbdda7a9eaf89f1efdac632ed914e53919083aa8ce, headerNum=1, 11ec9293c65bd3e0fe4d23c088d7fcf1deddadd241312b4fdbc85991fac97f9f - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_2930_slot_listed_but_untouched.json`) // block=1, gas used by execution: 25309, in header: 28637 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/varying_calldata_costs.json`) // block=1, gas used by execution: 31240, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_aborted_storage_access.json`) // block=1, receiptHash mismatch: 0574ca632811062d8709db6085aef9953a58cc20c1f2a9f2fd58973ee9fc43c5 != 3436c842cadfa64946a5dee36dd6ceaab7cbaab05b7ddcb3e8069b72bcf8620a, headerNum=1, 1efe1b367f53fbddf9e77735a144532932bd5aec9d496bccf4adc5ba0c4fe78f bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_all_transaction_types.json`) // block=1, gas used by execution: 214842, in header: 346330 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_cross_tx_storage_revert_to_zero.json`) // block=1, receiptHash mismatch: a6ac208e3a45183cb6c40f54919d814396fa128f0e07423325fedc58e67812fd != d856722f7ccb7e42263ba64d8b0ced05afff903fba0a0128425744f0de7dbf9d, headerNum=1, ebcd267ab1838b29e17d27dfaff171f57810d45ad5605f005c73ea8148a4cda8 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_precompile_funded.json`) // block=1, gas used by execution: 24512, in header: 29192 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7002/bal_7002_request_invalid.json`) // block=1, gas used by execution: 23584, in header: 24648 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7002/bal_7002_request_invalid.json`) // block=1, gas used by execution: 150272, in header: 56234 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_clear.json`) // block=1, gas used by execution: 57000, in header: 316980 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_create.json`) // block=1, gas used by execution: 28500, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_update.json`) // block=1, gas used by execution: 57000, in header: 316980 @@ -88,47 +81,12 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_null_address_delegation_no_code_change.json`) // block=1, gas used by execution: 28500, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_selfdestruct_to_7702_delegation.json`) // block=1, gas used by execution: 59724, in header: 158490 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_withdrawal_to_7702_delegation.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_7702_delegation_and_oog.json`) // block=1 (hash=0xb43d86b6cd3e1d8b8f7f4b684987fa47a4a669edcc6143787a9b72c62b53aff2): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-1471576661/bal - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23521, in header: 24800 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_call_with_value_in_static_context.json`) // block=1, gas used by execution: 1036027, in header: 1037307 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_callcode_7702_delegation_and_oog.json`) // block=1 (hash=0xd7ec8d3e0ee01f1a8e0a0e40b96cf75648113f08acc5879ec5b8967cba48eda4): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-3734185478/bal - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_callcode_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23521, in header: 24800 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create2_collision.json`) // block=1, receiptHash mismatch: 58c109854ad17a7e6cc36919cc07464496eba4562546763c5719a54dbcf83572 != c0f65f206dc904c838752c6d10e9e70f72efefaefb9f01c5793a730fc4d0ef43, headerNum=1, c650b7b61e4cee14205def343f64864259bcbeb9b127686d448f73dd086cd04b bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_and_oog.json`) // block=1, gas used by execution: 131488, in header: 30023 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_early_failure.json`) // block=1, gas used by execution: 131488, in header: 35026 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_oog_code_deposit.json`) // block=1, receiptHash mismatch: 432aa57d091596e6f080cb880aa2265c360922292588ee56480677ce29c931a1 != fd591027210a4da480975eab0be900db75960133ac29c6e4f479df76fe730315, headerNum=1, f0e3464870d4f197c97ef93cb229adb7fe0898b9333aaeb9da79f3095d8b38e8 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_selfdestruct_to_self_with_call.json`) // block=1, gas used by execution: 244192, in header: 75136 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_delegatecall_7702_delegation_and_oog.json`) // block=1 (hash=0xaebf8f0426fa03d66ccc61a55b6bdd38c00126d3f9e7c2a2270382de77ca22ec): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-498116264/bal - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_delegatecall_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23518, in header: 24797 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_selfdestruct_in_static_context.json`) // block=1, gas used by execution: 1036027, in header: 1037307 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_staticcall_7702_delegation_and_oog.json`) // block=1 (hash=0x8ef752f47ac4f89d34f8bfa2155254ef393d74b486d3f3866312f183fe9afb29): block access list mismatch; debug dumps in /Volumes/erigontests/mock-sentry-1337124827/bal - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_staticcall_no_delegation_and_oog_before_target_access.json`) // block=1, gas used by execution: 23518, in header: 24797 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_code_size/max_code_size_via_create.json`) // block=1, receiptHash mismatch: 764a45a52f65e30545bc1acec647b68bef0de17812d850a420716c19b6c19f37 != f2baac99015cfaaf4245fccad11e911bf1bba7f51678131b54269bf903cb6316, headerNum=1, 68ec0f21c9fdd7438f527c256bed133b73eb921c5db72957fa0516467b688547 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_initcode_size/max_initcode_size.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_initcode_size/max_initcode_size_gas_metering.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_initcode_size/max_initcode_size_via_create.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/exact_threshold_boundary.json`) // block=1, gas used by execution: 21040, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/floor_cost_not_reduced_by_refunds.json`) // block=1, gas used by execution: 61000, in header: 85000 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/maximum_calldata_size.json`) // block=1, gas used by execution: 6257840, in header: 9999944 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/memory_expansion_with_calldata.json`) // block=1, gas used by execution: 430600, in header: 676360 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/refund_calculated_from_execution_not_floor.json`) // block=1, gas used by execution: 41000, in header: 53000 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/additional_coverage/token_calculation_verification.json`) // block=1, gas used by execution: 22750, in header: 27400 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/eip_mainnet/eip_7976.json`) // block=1, gas used by execution: 21010, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/execution_gas/full_gas_consumption.json`) // block=1, gas used by execution: 201000, in header: 309000 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/execution_gas/gas_consumption_below_data_floor.json`) // block=1, gas used by execution: 21063, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/refunds/gas_refunds_from_data_floor.json`) // block=1, gas used by execution: 215694, in header: 242696 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_0.json`) // block=1, gas used by execution: 21010, in header: 21064 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_1_type_2.json`) // block=1, gas used by execution: 380680, in header: 814088 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_3.json`) // block=1, gas used by execution: 380680, in header: 814088 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/transaction_validity/transaction_validity_type_4.json`) // block=1, gas used by execution: 1763920, in header: 3027272 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/access_list_floor_cost_with_calldata.json`) // block=1, gas used by execution: 26900, in header: 30728 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/access_list_token_calculation.json`) // block=1, gas used by execution: 25300, in header: 28628 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/duplicate_access_list_entries.json`) // block=1, gas used by execution: 29600, in header: 36256 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/access_list_cost/large_access_list_cost.json`) // block=1, gas used by execution: 80500, in header: 138100 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/eip_mainnet/access_list_data_cost_edge_cases.json`) // block=1, gas used by execution: 32900, in header: 44420 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/eip_mainnet/access_list_gas_cost.json`) // block=1, gas used by execution: 29600, in header: 36256 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/transaction_validity/mixed_zero_nonzero_bytes_floor_cost.json`) // block=1, gas used by execution: 126400, in header: 230080 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7981_increase_access_list_cost/transaction_validity/valid_gas_limits_with_access_list.json`) // block=1, gas used by execution: 25300, in header: 28628 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/dupn/dupn_stack_underflow.json`) // block=1, receiptHash mismatch: 6ebeb82e2fd4ad8ef581ba011ed8590752fbb658e86bb4f29d186cba3f7b1357 != 496ca8c76f744094f9ba323bd2996f088f7609d26fc17a648298dac6203189a0, headerNum=1, a9c78e38550bc1045539456afe1a06c8086c5a708bac1d17b51976ea05acf949 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/exchange/exchange_invalid_immediate_aborts.json`) // block=1, receiptHash mismatch: 6ebeb82e2fd4ad8ef581ba011ed8590752fbb658e86bb4f29d186cba3f7b1357 != 496ca8c76f744094f9ba323bd2996f088f7609d26fc17a648298dac6203189a0, headerNum=1, 3942d6681902bdc203aaac09522f161eca9fcf30c763f74f3307264d99d8f5f9 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/exchange/exchange_stack_underflow.json`) // block=1, receiptHash mismatch: 6ebeb82e2fd4ad8ef581ba011ed8590752fbb658e86bb4f29d186cba3f7b1357 != 496ca8c76f744094f9ba323bd2996f088f7609d26fc17a648298dac6203189a0, headerNum=1, 648868b84582031adf8c7ca3a0b40b14879f1cf2b8c55dc1ad8b57c89f51acf4 @@ -144,10 +102,6 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_value_to_self_destructed_same_tx_account.json`) // block=1, gas used by execution: 169056, in header: 46874 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_zero_value_to_self_destructed_same_tx_account.json`) // block=1, gas used by execution: 131488, in header: 35173 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/create_insufficient_balance_returns_reservoir.json`) // block=1, gas used by execution: 169056, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_applied_to_sender_refund.json`) // block=1, gas used by execution: 61960, in header: 86536 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_exceeding_tx_gas_limit_cap.json`) // block=1, gas used by execution: 10493600, in header: 16777160 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_higher_than_execution_with_state_ops.json`) // block=1, gas used by execution: 61960, in header: 86536 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_calldata_floor/calldata_floor_independent_of_state_gas.json`) // block=1, gas used by execution: 41480, in header: 53768 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/code_deposit_halt_discards_initcode_state_gas.json`) // block=1, receiptHash mismatch: 6109c57d8983f520f4b510024a1760aff8d920bb293bbc00c30abdac956b3b2f != c7cd454c1e2d8f590ccd8205e653d2649edb81e2ee1259782fb3af690d5648d8, headerNum=1, 89a4abe5fd3c9d1b72b7dc5e1a8aba9fd3164ec844156307f23ceac5b92181a5 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/code_deposit_oog_preserves_parent_reservoir.json`) // block=1, receiptHash mismatch: 87eb1d5efeccbf21568dc8ba422e793d3dc3ca7bea2c064adcd6d4bf89c1f636 != 13caceefb2a53e479514f660e5d50b365ead011387defffccca23e4d8d03c5f4, headerNum=1, 59aa99f406a68008b33cc2807b9bd2988a55798b270b671b549c2923b703baad bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create2_address_collision.json`) // block=1, receiptHash mismatch: ed982053122348c512e18c1ac8827d4e10ef91c2705616cd872dc2ad2a0b03dd != 7637ffd9bbf631be43879032b61a2d3ece5335e64500422749a7ea9001482b68, headerNum=1, c148d12a917d4fc0c5dafc6ffa352ee44d216546824653e3b697b3ac7dbf9ac1 @@ -162,15 +116,11 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/failed_create_header_gas_used.json`) // block=1, receiptHash mismatch: 9adb08ce7ee8cdea800360deb4ac97ab74ab228a85c1c3756ad1b8c6b675adf3 != 769baff2fb1b33f057f8234087ba691afc2a7e92d81d7dc023a960fc22378d16, headerNum=1, 5c835fc83a5400f48feeb928f38e7f3dc05966214a9dbeee905b3bc0e6089fd6 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/inner_create_fail_refunds_in_creation_tx.json`) // block=1, gas used by execution: 525952, in header: 131488 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/inner_create_succeeds_code_deposit_state_gas.json`) // block=1, gas used by execution: 264150, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/max_initcode_size_gas_metering_via_create.json`) // block=1, gas used by execution: 676630, in header: 4215304 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/nested_create_code_deposit_cannot_borrow_parent_gas.json`) // block=1, gas used by execution: 131488, in header: 30620 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/nested_create_fail_parent_revert_state_gas.json`) // block=1, receiptHash mismatch: 7bcf94219e25ac0732ab6b16e86421b8ca83921986485c99782a0be5579d3a87 != c55cbaff662c04ece0a9c550a57a05b480590f30131afd89fab8b99a29f685a5, headerNum=1, ea6f529ce05e9a22fe8b59ace8401967a32ff6bfaf0f80a9a3a044b3cdd1cc29 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/oversized_initcode_opcode_no_state_gas.json`) // block=1, gas used by execution: 676630, in header: 4215304 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/oversized_initcode_tx_no_state_gas.json`) // block=1, gas used by execution: 676630, in header: 4215304 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/selfdestruct_in_create_tx_initcode.json`) // block=1, gas used by execution: 262976, in header: 131488 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/auth_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 102138, in header: 233626 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/call_new_account_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/calldata_floor_enforced_with_state_gas.json`) // block=1, gas used by execution: 25000, in header: 27400 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/cpsb_underflow_boundary.json`) // block=1, gas used by execution: 37568, in header: 26006 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/create_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/pricing_at_various_gas_limits.json`) // block=1, gas used by execution: 37568, in header: 381632 @@ -180,8 +130,6 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/refund_with_reservoir_state_gas.json`) // block=1, gas used by execution: 37568, in header: 26112 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/selfdestruct_new_beneficiary_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/sstore_refund_scales_with_cpsb.json`) // block=1, gas used by execution: 37568, in header: 26112 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/access_list_gas_is_regular_not_state.json`) // block=1, gas used by execution: 23400, in header: 24680 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/access_list_warm_savings_stay_regular.json`) // block=1, gas used by execution: 25506, in header: 28834 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/creation_tx_failure_preserves_intrinsic_state_gas.json`) // block=1, receiptHash mismatch: 655ba40a763440741644e1cc9308e3161cadd08a84571fb49ff3311789fb61e2 != 34f408ef6c0c284659b1f6f2bb262a45afacfb0ada3448163a7c3bf0520d86ee, headerNum=1, 2ab3e39efd48616245f9037e31450045ef402c07744f16f7d11d674505c802fc bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/creation_tx_regular_check_subtracts_intrinsic_state.json`) // intrinsic gas too low: have 46800, want 161488 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/top_level_failure_refunds_execution_state_gas.json`) // block=1, receiptHash mismatch: 45cbf3c1eacc7b09beaf9eb699a979e045c93d6e3857920c0b33a39b3727d43d != 6109c57d8983f520f4b510024a1760aff8d920bb293bbc00c30abdac956b3b2f, headerNum=1, 6682826169809fc2384bda92bdbe0e432f1d81c87618bc7f72acd977649b6812 @@ -223,30 +171,16 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_reservoir_spillover.json`) // block=1, gas used by execution: 37568, in header: 26112 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_sub_frame_revert.json`) // block=1, gas used by execution: 75136, in header: 76137 bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_then_reset.json`) // block=1, gas used by execution: 75136, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_state_gas_all_tx_types.json`) // block=1, gas used by execution: 45006, in header: 61134 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_state_gas_all_tx_types.json`) // block=1, gas used by execution: 91572, in header: 354548 bt.SkipLoad(`^for_amsterdam/berlin/eip2929_gas_cost_increases/create/create_insufficient_balance.json`) // block=1, gas used by execution: 169056, in header: 47918 bt.SkipLoad(`^for_amsterdam/berlin/eip2929_gas_cost_increases/create/create_nonce_overflow.json`) // block=1, gas used by execution: 169056, in header: 47918 - bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/acl/account_storage_warm_cold_state.json`) // block=1, gas used by execution: 37568, in header: 38391 - bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/acl/repeated_address_acl.json`) // block=1, receiptHash mismatch: fc6a9666511f1769ef9170ad67316857f70034c974f82e8bb29d8718785410f9 != 925c49d754f5ae98a0f4b9809b1b6374183eb23049041353208a309fd19cbf87, headerNum=1, 65b22b0f8a3bb6791b7a17829222c06cf3c3cea1120923c461f2a50e64d01d41 - bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/acl/transaction_intrinsic_gas_cost.json`) // block=1, gas used by execution: 29600, in header: 36256 - bt.SkipLoad(`^for_amsterdam/berlin/eip2930_access_list/tx_intrinsic_gas/tx_intrinsic_gas.json`) // block=1, gas used by execution: 134676, in header: 231040 - bt.SkipLoad(`^for_amsterdam/byzantium/eip196_ec_add_mul/ecadd/invalid.json`) // block=1, gas used by execution: 26609, in header: 29192 bt.SkipLoad(`^for_amsterdam/cancun/create/create_oog_from_eoa_refunds/create_oog_from_eoa_refunds.json`) // block=1, receiptHash mismatch: ba9991e573821b0c234e361e68d2fe4b0fc940f8380d61fc18ee6ceb963b7094 != 91e4445a3ed8b70b41017281d14d817b16413ae3d96c8afaabd680ec95ff42d9, headerNum=1, 0d4c5d4b752c49e59f005ae61edeb5fadca9a7609e9d100e14097e7f492b1ff5 - bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_create_contexts/tstore_rollback_on_failed_create.json`) // block=1, gas used by execution: 16776219, in header: 16776220 + bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_create_contexts/tstore_rollback_on_failed_create.json`) // block=1, receiptHash mismatch: e016160cb1798fde7eabcfd05c6eb41e1a55100f4437aa6196c556af22ec8ce7 != e24f8d65696478f8ffc1cf51d0139830b695891444efba6397cad40130cad1a2, headerNum=1, dba1442a797df47e541242b968d89018c9546b9589f8e113f2743ea635a7460b bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_selfdestruct/reentrant_selfdestructing_call.json`) // block=1, gas used by execution: 335764, in header: 112704 - bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/beacon_root_contract_timestamps.json`) // block=1, receiptHash mismatch: a08468bbc451b952113931bb879542c55e3d7b2ccf4deb042d0b94f8c57f86df != e7ccd1a812b8e285ebd71350997600f2cb445cbc4418c19232a5392071516914, headerNum=1, 310f42900fe908a6ae29313e40fdd04368834e2dabb4431167929c4e7c718344 - bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/beacon_root_equal_to_timestamp.json`) // block=1, receiptHash mismatch: a08468bbc451b952113931bb879542c55e3d7b2ccf4deb042d0b94f8c57f86df != e7ccd1a812b8e285ebd71350997600f2cb445cbc4418c19232a5392071516914, headerNum=1, d7f435eb5a5dc52f308311a30cd9db80eda1320c56953dbafc55bb5c25890950 - bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/calldata_lengths.json`) // block=1, gas used by execution: 36819, in header: 86536 - bt.SkipLoad(`^for_amsterdam/cancun/eip4788_beacon_root/beacon_root_contract/tx_to_beacon_root_contract.json`) // block=1, gas used by execution: 27660, in header: 33036 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blob_txs/blob_gas_subtraction_tx.json`) // block=1, receiptHash mismatch: 6a54bbdc096ab24565de7bf4759b6f7be19d810de95cf864ee13dd4b708adde5 != 5dff71537f505e5214e9fa0e1ad143f08fe119b94a30f008761c18c57ffa621e, headerNum=1, 1385b20a9eb8dd45b665fea39481e78ea47dbc1d81acb82673d9288ad7910d71 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blob_txs/sufficient_balance_blob_tx.json`) // block=1, gas used by execution: 27200, in header: 32576 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blob_txs/sufficient_balance_blob_tx_pre_fund_tx.json`) // block=1, gas used by execution: 48200, in header: 53576 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blobhash_opcode/blobhash_invalid_blob_index.json`) // block=1, gas used by execution: 413248, in header: 88332 bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blobhash_opcode/blobhash_scenarios.json`) // block=1, gas used by execution: 450816, in header: 225408 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/point_evaluation_precompile/invalid_inputs.json`) // block=1, receiptHash mismatch: 80103092893bf0d37059859da6b918e6ab07d3b2cfba5ae88fd16923579da4b8 != 9d85dc4a419a68fa73ebdfd5562cd949bfaa2d201e39a6af221e303cbe0566bc, headerNum=1, 49791b8a87b0af17867656adad724e157f4ddb42820fcc07a63a7f853e19f949 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/point_evaluation_precompile/tx_entry_point.json`) // block=1, gas used by execution: 94148, in header: 105668 bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_huge_memory_expansion.json`) // block=1, receiptHash mismatch: 56fe7a0c09434e88468b961fffa5d328dd45d99b0a8d254178c45b1c33ab7a12 != 98ff002f51d977b4e85bd9a688d348d42c26f3b2f31fbfe9f005555adf3e5027, headerNum=1, f5309ec689b572ac095a551de1ab894b6782605b03012a48469f0f961cdd3e94 - bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_memory_expansion.json`) // block=1, gas used by execution: 47635, in header: 59154 + bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_memory_expansion.json`) // block=1, receiptHash mismatch: d4ae000564ebd6f4e4df0bcb4524237f62c738385bcb26ef080b2905920cfed5 != 6760bb71c1b6e83941816e9179aa72b55178a33ce8b352e163c13e880e034c71, headerNum=1, 303a329a5ff7b2dfa9676bf4fd95e37db7228714f90508fca5f20b6f434991a8 bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/dynamic_create2_selfdestruct_collision/dynamic_create2_selfdestruct_collision.json`) // block=1, gas used by execution: 795972, in header: 473083 bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/dynamic_create2_selfdestruct_collision/dynamic_create2_selfdestruct_collision_multi_tx.json`) // block=1, gas used by execution: 570564, in header: 500666 bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/dynamic_create2_selfdestruct_collision/dynamic_create2_selfdestruct_collision_two_different_transactions.json`) // block=1, gas used by execution: 896936, in header: 702052 @@ -272,43 +206,15 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/frontier/create/create_suicide_during_init/create_suicide_during_transaction_create.json`) // block=1, gas used by execution: 169056, in header: 46953 bt.SkipLoad(`^for_amsterdam/frontier/create/create_suicide_store/create_suicide_store.json`) // block=1, gas used by execution: 413248, in header: 112704 bt.SkipLoad(`^for_amsterdam/frontier/opcodes/all_opcodes/cover_revert.json`) // block=1, gas used by execution: 169056, in header: 131488 - bt.SkipLoad(`^for_amsterdam/frontier/precompiles/ripemd/precompiles.json`) // block=1, gas used by execution: 421000, in header: 661000 bt.SkipLoad(`^for_amsterdam/frontier/scenarios/scenarios/scenarios.json`) // block=21, gas used by execution: 131488, in header: 50495 - bt.SkipLoad(`^for_amsterdam/istanbul/eip1344_chainid/chainid/chainid.json`) // block=1, gas used by execution: 45005, in header: 61133 - bt.SkipLoad(`^for_amsterdam/osaka/eip7823_modexp_upper_bounds/modexp_upper_bounds/modexp_upper_bounds.json`) // block=1, gas used by execution: 150272, in header: 158280 - bt.SkipLoad(`^for_amsterdam/osaka/eip7825_transaction_gas_limit_cap/tx_gas_limit/tx_gas_limit_cap_contract_creation.json`) // block=1, gas used by execution: 1331720, in header: 2118152 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/modexp_gas_usage_contract_wrapper.json`) // block=1, gas used by execution: 28521, in header: 32520 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/modexp_legacy_oversized_inputs.json`) // block=1, gas used by execution: 27880, in header: 31240 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/modexp_used_in_transaction_entry_points.json`) // block=1, gas used by execution: 25350, in header: 32520 - bt.SkipLoad(`^for_amsterdam/osaka/eip7883_modexp_gas_increase/modexp_thresholds/vectors_from_eip.json`) // block=1, gas used by execution: 150272, in header: 158280 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_at_rlp_limit_with_logs.json`) // block=1, gas used by execution: 84572150, in header: 537292760 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_at_rlp_limit_with_withdrawals.json`) // block=1, gas used by execution: 84551780, in header: 537275792 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_at_rlp_size_limit_boundary.json`) // block=1, gas used by execution: 84552280, in header: 537278992 - bt.SkipLoad(`^for_amsterdam/osaka/eip7934_block_rlp_limit/max_block_rlp_size/block_rlp_size_at_limit_with_all_typed_transactions.json`) // block=1, gas used by execution: 84552420, in header: 537279888 - bt.SkipLoad(`^for_amsterdam/osaka/eip7951_p256verify_precompiles/p256verify/gas.json`) // block=1, gas used by execution: 28105, in header: 31240 - bt.SkipLoad(`^for_amsterdam/osaka/eip7951_p256verify_precompiles/p256verify/precompile_as_tx_entry_point.json`) // block=1, gas used by execution: 30436, in header: 31240 + bt.SkipLoad(`^for_amsterdam/istanbul/eip1344_chainid/chainid/chainid.json`) // block=1, gas used by execution: 91572, in header: 354548 bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/collision_selfdestruct/selfdestruct_after_create2_collision.json`) // block=1, gas used by execution: 499019, in header: 409422 bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/initcollision/init_collision_create_opcode.json`) // block=1, receiptHash mismatch: 524db87016bef6d465f58a76b2d203a5aa7530ed58655fc1a222fc976da98830 != 0ef8df661f25e664b1912098fbf14882312c23e5318ea1af9948df4ad71304eb, headerNum=1, 441aaedd3994f8ef204e166eefeb7cfc63b2250c0423d68737a120ebba7f117e bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/revert_in_create/create2_collision_storage.json`) // block=1, receiptHash mismatch: c5beba07298d35464a539c2062ecf900c130d2400e66393b762a20377e6af894 != e019c41e16c55b80810e927d1bba60fc149d3da257bd5a4f83260142b1debc8f, headerNum=1, 2c115de7d2427ab2aac6d6d93890448d389878a11e1cb6dec62a5c7ac91e75ab bt.SkipLoad(`^for_amsterdam/paris/security/selfdestruct_balance_bug/tx_selfdestruct_balance_bug.json`) // block=1, gas used by execution: 266498, in header: 128408 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1add/gas.json`) // block=1, gas used by execution: 26985, in header: 37384 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1add/invalid.json`) // block=1, gas used by execution: 29290, in header: 37384 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1msm/call_types.json`) // block=1, gas used by execution: 1111968, in header: 1331720 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1msm/valid.json`) // block=1, gas used by execution: 751712, in header: 1045000 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g1mul/invalid.json`) // block=1, gas used by execution: 25941, in header: 27144 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2add/gas.json`) // block=1, gas used by execution: 28282, in header: 53768 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2add/invalid.json`) // block=1, gas used by execution: 37640, in header: 53768 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2msm/valid.json`) // block=1, gas used by execution: 1382442, in header: 1864200 - bt.SkipLoad(`^for_amsterdam/prague/eip2537_bls_12_381_precompiles/bls12_g2mul/invalid.json`) // block=1, gas used by execution: 26209, in header: 31240 - bt.SkipLoad(`^for_amsterdam/prague/eip6110_deposits/deposits/deposit.json`) // block=1, gas used by execution: 11619209, in header: 11656200 - bt.SkipLoad(`^for_amsterdam/prague/eip7002_el_triggerable_withdrawals/modified_withdrawal_contract/extra_withdrawals.json`) // block=1, gas used by execution: 329850, in header: 368760 + bt.SkipLoad(`^for_amsterdam/prague/eip6110_deposits/deposits/deposit.json`) // block=1, gas used by execution: 112704, in header: 95374 bt.SkipLoad(`^for_amsterdam/prague/eip7002_el_triggerable_withdrawals/withdrawal_requests/withdrawal_requests.json`) // block=1, gas used by execution: 225408, in header: 150272 bt.SkipLoad(`^for_amsterdam/prague/eip7251_consolidations/consolidations/consolidation_requests.json`) // block=1, gas used by execution: 262976, in header: 187840 - bt.SkipLoad(`^for_amsterdam/prague/eip7251_consolidations/modified_consolidation_contract/extra_consolidations.json`) // block=1, gas used by execution: 22020, in header: 27144 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/execution_gas/gas_consumption_below_data_floor.json`) // block=1, gas used by execution: 21063, in header: 21064 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_0.json`) // block=1, gas used by execution: 21010, in header: 21064 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_3.json`) // block=1, gas used by execution: 380620, in header: 814088 - bt.SkipLoad(`^for_amsterdam/prague/eip7623_increase_calldata_cost/transaction_validity/transaction_validity_type_4.json`) // block=1, gas used by execution: 1763860, in header: 3027272 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/gas/account_warming.json`) // block=1, gas used by execution: 185492, in header: 316980 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/creating_delegation_designation_contract.json`) // block=1, receiptHash mismatch: eb0a2410285068047569b15f5785dacab88b92c4551f532189e41037162268dc != f5610820eb298919377f6d68f2eb097e952382f07a765552fb25d2a0b8bdad59, headerNum=1, c839439445443752f8b49facf7f0eff1f457461e8346d022e9b5683713ffd258 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/delegation_clearing.json`) // block=1, gas used by execution: 102138, in header: 233626 @@ -349,13 +255,12 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_to_precompile.json`) // block=1, gas used by execution: 64570, in header: 196058 bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_to_static_reentry.json`) // block=1, gas used by execution: 140147, in header: 233626 bt.SkipLoad(`^for_amsterdam/shanghai/eip3855_push0/push0/push0_contracts.json`) // block=1, receiptHash mismatch: 777f1c1c378807634128348e4f0eeca6a0e7f516ea411690ca04266323f671a4 != 2f13c48591f063e30a792a180116e5ef2611efc62565ec81f9cbe853e23bc631, headerNum=1, a89980a26041d5d56ba7192f619f4dc13d8bed40c41fff9f8be25b15b5a207eb - bt.SkipLoad(`^for_amsterdam/shanghai/eip3860_initcode/initcode/contract_creating_tx.json`) // block=1, gas used by execution: 1986990, in header: 3166728 bt.SkipLoad(`^for_amsterdam/shanghai/eip3860_initcode/initcode/create2_oversized_initcode_with_insufficient_balance.json`) // block=1, gas used by execution: 169056, in header: 37568 bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/initcode_selfdestruct_to_self.json`) // block=1, gas used by execution: 131488, in header: 35040 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_state_access_boundary.json`) // block=1, gas used by execution: 31024, in header: 32304 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_precompile_state_access_boundary.json`) // block=1, gas used by execution: 31024, in header: 32304 + bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_state_access_boundary.json`) // block=1, gas used by execution: 157316, in header: 37803 + bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_precompile_state_access_boundary.json`) // block=1, gas used by execution: 135010, in header: 38869 bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_self.json`) // block=1, gas used by execution: 133836, in header: 35188 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_system_contract.json`) // block=1, gas used by execution: 31024, in header: 32304 + bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_system_contract.json`) // block=1, gas used by execution: 152620, in header: 38869 bt.Walk(t, dir, func(t *testing.T, name string, test *testutil.BlockTest) { // import pre accounts & construct test genesis block & state root From 399c05f8b010dab83a61edfe8890b633949b66d4 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:27:13 +0200 Subject: [PATCH 12/31] add back reverted submodule version update --- execution/tests/execution-spec-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/tests/execution-spec-tests b/execution/tests/execution-spec-tests index d35196c3079..ec66995f0de 160000 --- a/execution/tests/execution-spec-tests +++ b/execution/tests/execution-spec-tests @@ -1 +1 @@ -Subproject commit d35196c3079eb31d73bfdb8e5fa0afe85250ab97 +Subproject commit ec66995f0debb19fb1e0e4499cf7e4a172b46028 From c13e8a25fd602274cc8b308606fc8442dd9cba77 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:06:14 +0000 Subject: [PATCH 13/31] fix --- execution/protocol/mdgas/intrinsic_gas.go | 41 ++------- .../protocol/mdgas/intrinsic_gas_test.go | 85 ++++--------------- 2 files changed, 25 insertions(+), 101 deletions(-) diff --git a/execution/protocol/mdgas/intrinsic_gas.go b/execution/protocol/mdgas/intrinsic_gas.go index 785e2afeffc..2bef55de4e1 100644 --- a/execution/protocol/mdgas/intrinsic_gas.go +++ b/execution/protocol/mdgas/intrinsic_gas.go @@ -125,39 +125,6 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) return IntrinsicGasCalcResult{}, true } } - if args.IsEIP7623 { - var tokenLen uint64 - var costPerToken uint64 - if args.IsEIP7976 { - // EIP-7976: floor_tokens = total_bytes * 4, cost_per_token = 16 - // => 64 gas per byte (both zero and non-zero) - var overflow bool - tokenLen, overflow = math.SafeMul(dataLen, 4) - if overflow { - return IntrinsicGasCalcResult{}, true - } - costPerToken = params.TxTotalCostFloorPerTokenEIP7976 - } else { - // EIP-7623: tokens = zero_bytes + 4*nonzero_bytes = dataLen + 3*nz - nzTokens, overflow := math.SafeMul(3, nz) - if overflow { - return IntrinsicGasCalcResult{}, true - } - tokenLen, overflow = math.SafeAdd(dataLen, nzTokens) - if overflow { - return IntrinsicGasCalcResult{}, true - } - costPerToken = params.TxTotalCostFloorPerToken - } - dataGas, overflow := math.SafeMul(tokenLen, costPerToken) - if overflow { - return IntrinsicGasCalcResult{}, true - } - result.FloorGasCost, overflow = math.SafeAdd(result.FloorGasCost, dataGas) - if overflow { - return IntrinsicGasCalcResult{}, true - } - } } if args.AccessListLen > 0 { product, overflow := math.SafeMul(args.AccessListLen, params.TxAccessListAddressGas) @@ -252,7 +219,13 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) } } } - if args.IsEIP7976 { + // EIP-7976 and EIP-7981 share the same per-token rate (16 gas/token), and + // EIP-7981 spec requires EIP-7976 as a precondition. Selecting the rate on + // either flag keeps the access-list floor surcharge (always charged at + // EIP-7976 rate in RegularGas above) consistent with the FloorGasCost rate + // even in the unsupported configuration where EIP-7981 is enabled without + // EIP-7976. + if args.IsEIP7976 || args.IsEIP7981 { costPerToken = params.TxTotalCostFloorPerTokenEIP7976 } else { costPerToken = params.TxTotalCostFloorPerToken diff --git a/execution/protocol/mdgas/intrinsic_gas_test.go b/execution/protocol/mdgas/intrinsic_gas_test.go index 2fcc3159797..bf2d3af64c0 100644 --- a/execution/protocol/mdgas/intrinsic_gas_test.go +++ b/execution/protocol/mdgas/intrinsic_gas_test.go @@ -109,16 +109,10 @@ func TestZeroDataIntrinsicGas(t *testing.T) { assert.Equal(params.TxGas, result.FloorGasCost) } -<<<<<<< HEAD // TestEIP7976FloorCost covers EIP-7976 (Increase Calldata Floor Cost): every // calldata byte (zero or non-zero) contributes TxStandardTokensPerByte tokens, // each costing TxTotalCostFloorPerTokenEIP7976 gas. func TestEIP7976FloorCost(t *testing.T) { -======= -func TestEIP7976FloorCost(t *testing.T) { - // EIP-7976 floor: 64 gas per byte (both zero and non-zero), - // computed as floor_tokens = total_bytes * 4, cost_per_token = 16. ->>>>>>> e3bbdc7ec7b05c607f0b596eb264fbb1f95bc723 cases := map[string]struct { dataLen uint64 dataNonZeroLen uint64 @@ -133,37 +127,23 @@ func TestEIP7976FloorCost(t *testing.T) { // 32 zero bytes: floor_tokens = 32*4 = 128, floor = 128*16 = 2048 dataLen: 32, dataNonZeroLen: 0, - expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 + expectedFloor: params.TxGas + 32*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 }, "all non-zero bytes": { -<<<<<<< HEAD // Same floor as all-zero (byte value doesn't matter) dataLen: 32, dataNonZeroLen: 32, - expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, - }, - "mixed bytes": { - dataLen: 32, - dataNonZeroLen: 12, - expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, -======= - // 32 non-zero bytes: floor_tokens = 32*4 = 128, floor = 128*16 = 2048 - // Key property: same floor as all-zero (byte value doesn't matter) - dataLen: 32, - dataNonZeroLen: 32, - expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 + expectedFloor: params.TxGas + 32*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, "mixed bytes": { - // 20 zero + 12 non-zero = 32 total: floor_tokens = 32*4 = 128, floor = 128*16 = 2048 dataLen: 32, dataNonZeroLen: 12, - expectedFloor: params.TxGas + 32*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 2048 = 23048 ->>>>>>> e3bbdc7ec7b05c607f0b596eb264fbb1f95bc723 + expectedFloor: params.TxGas + 32*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, "single byte non-zero": { dataLen: 1, dataNonZeroLen: 1, - expectedFloor: params.TxGas + 1*4*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 64 = 21064 + expectedFloor: params.TxGas + 1*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, // 21000 + 64 = 21064 }, } @@ -187,16 +167,9 @@ func TestEIP7976FloorCost(t *testing.T) { } } -<<<<<<< HEAD // TestEIP7976VsEIP7623Floor verifies EIP-7976 floors are strictly greater // than EIP-7623 floors for the same calldata. func TestEIP7976VsEIP7623Floor(t *testing.T) { -======= -func TestEIP7976VsEIP7623Floor(t *testing.T) { - // Compare EIP-7976 vs EIP-7623 floor costs for the same data. - // EIP-7976 is strictly greater than EIP-7623 for any non-empty calldata - // (64 > 10 for zero bytes, 64 > 40 for non-zero bytes). ->>>>>>> e3bbdc7ec7b05c607f0b596eb264fbb1f95bc723 assert := assert.New(t) // 32 non-zero bytes: @@ -221,21 +194,11 @@ func TestEIP7976VsEIP7623Floor(t *testing.T) { }) assert.False(overflow) -<<<<<<< HEAD assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerToken, result7623.FloorGasCost) assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerTokenEIP7976, result7976.FloorGasCost) assert.Greater(result7976.FloorGasCost, result7623.FloorGasCost) // 32 zero bytes: -======= - assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerToken, result7623.FloorGasCost) // 21000+1280=22280 - assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerTokenEIP7976, result7976.FloorGasCost) // 21000+2048=23048 - assert.Greater(result7976.FloorGasCost, result7623.FloorGasCost) - - // 32 zero bytes: - // EIP-7623: tokens = 32 + 3*0 = 32, floor = 32*10 = 320 - // EIP-7976: tokens = 32*4 = 128, floor = 128*16 = 2048 ->>>>>>> e3bbdc7ec7b05c607f0b596eb264fbb1f95bc723 result7623z, overflow := CalcIntrinsicGas(IntrinsicGasCalcArgs{ Data: make([]byte, 32), DataNonZeroLen: 0, @@ -255,7 +218,6 @@ func TestEIP7976VsEIP7623Floor(t *testing.T) { }) assert.False(overflow) -<<<<<<< HEAD assert.Equal(params.TxGas+32*params.TxTotalCostFloorPerToken, result7623z.FloorGasCost) assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerTokenEIP7976, result7976z.FloorGasCost) assert.Greater(result7976z.FloorGasCost, result7623z.FloorGasCost) @@ -268,12 +230,6 @@ func TestEIP7976VsEIP7623Floor(t *testing.T) { // access list data contributes to the floor calculation, and the access // list data cost is always charged in the standard intrinsic gas path. func TestEIP7981IntrinsicGas(t *testing.T) { - const ( - floorCostPerToken = 16 - tokensPerByte = 4 - addressBytes = 20 - storageKeyBytes = 32 - ) cases := map[string]struct { dataLen uint64 dataNonZeroLen uint64 @@ -296,9 +252,12 @@ func TestEIP7981IntrinsicGas(t *testing.T) { accessListLen: 1, storageKeysLen: 0, // regular = 21000 + 2400 + (20*4)*16 = 21000 + 2400 + 1280 - expectedRegularGas: params.TxGas + params.TxAccessListAddressGas + addressBytes*tokensPerByte*floorCostPerToken, + expectedRegularGas: params.TxGas + + params.TxAccessListAddressGas + + params.TxAccessListAddressBytes*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, // floor = 21000 + (20*4)*16 = 21000 + 1280 - expectedFloorGasCost: params.TxGas + addressBytes*tokensPerByte*floorCostPerToken, + expectedFloorGasCost: params.TxGas + + params.TxAccessListAddressBytes*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, "access list with storage keys": { dataLen: 0, @@ -310,8 +269,9 @@ func TestEIP7981IntrinsicGas(t *testing.T) { expectedRegularGas: params.TxGas + params.TxAccessListAddressGas + 2*params.TxAccessListStorageKeyGas + - (addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, - expectedFloorGasCost: params.TxGas + (addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + (params.TxAccessListAddressBytes+2*params.TxAccessListStorageKeyBytes)*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, + expectedFloorGasCost: params.TxGas + + (params.TxAccessListAddressBytes+2*params.TxAccessListStorageKeyBytes)*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, "calldata only all non-zero": { dataLen: 32, @@ -321,7 +281,7 @@ func TestEIP7981IntrinsicGas(t *testing.T) { // regular = 21000 + 32*16 = 21512 expectedRegularGas: params.TxGas + 32*params.TxDataNonZeroGasEIP2028, // floor = 21000 + (32*4)*16 = 21000 + 2048 - expectedFloorGasCost: params.TxGas + 32*tokensPerByte*floorCostPerToken, + expectedFloorGasCost: params.TxGas + 32*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, "calldata only all zero bytes": { dataLen: 32, @@ -331,7 +291,7 @@ func TestEIP7981IntrinsicGas(t *testing.T) { // regular = 21000 + 32*4 = 21128 expectedRegularGas: params.TxGas + 32*params.TxDataZeroGas, // EIP-7976: zero bytes also cost 4 tokens each for the floor => 21000 + (32*4)*16 = 23048 - expectedFloorGasCost: params.TxGas + 32*tokensPerByte*floorCostPerToken, + expectedFloorGasCost: params.TxGas + 32*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, "calldata and access list": { dataLen: 32, @@ -344,9 +304,10 @@ func TestEIP7981IntrinsicGas(t *testing.T) { 32*params.TxDataNonZeroGasEIP2028 + params.TxAccessListAddressGas + 2*params.TxAccessListStorageKeyGas + - (addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + (params.TxAccessListAddressBytes+2*params.TxAccessListStorageKeyBytes)*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, // floor tokens = 32*4 + 84*4 = 128 + 336 = 464; floor = 21000 + 464*16 = 28424 - expectedFloorGasCost: params.TxGas + (32+addressBytes+2*storageKeyBytes)*tokensPerByte*floorCostPerToken, + expectedFloorGasCost: params.TxGas + + (32+params.TxAccessListAddressBytes+2*params.TxAccessListStorageKeyBytes)*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, }, } @@ -389,15 +350,5 @@ func TestEIP7981NotActive(t *testing.T) { // Regular: 21000 + 32*16 + 2400 + 2*1900 = 27712 (no access list data floor charge) assert.Equal(t, params.TxGas+32*params.TxDataNonZeroGasEIP2028+params.TxAccessListAddressGas+2*params.TxAccessListStorageKeyGas, result.RegularGas) // Floor (EIP-7976, access list not included): 21000 + (32*4)*16 = 23048 - assert.Equal(t, params.TxGas+32*4*params.TxTotalCostFloorPerTokenEIP7976, result.FloorGasCost) -} -======= - assert.Equal(params.TxGas+32*params.TxTotalCostFloorPerToken, result7623z.FloorGasCost) // 21000+320=21320 - assert.Equal(params.TxGas+128*params.TxTotalCostFloorPerTokenEIP7976, result7976z.FloorGasCost) // 21000+2048=23048 - assert.Greater(result7976z.FloorGasCost, result7623z.FloorGasCost) - - // Standard gas should be the same regardless of EIP-7976 - assert.Equal(result7623.RegularGas, result7976.RegularGas) - assert.Equal(result7623z.RegularGas, result7976z.RegularGas) + assert.Equal(t, params.TxGas+32*params.TxStandardTokensPerByte*params.TxTotalCostFloorPerTokenEIP7976, result.FloorGasCost) } ->>>>>>> e3bbdc7ec7b05c607f0b596eb264fbb1f95bc723 From ec5145baa73ffa3b12a082ca5f66e3a9d54a79ac Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:22:40 +0000 Subject: [PATCH 14/31] fix --- execution/protocol/mdgas/intrinsic_gas.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/execution/protocol/mdgas/intrinsic_gas.go b/execution/protocol/mdgas/intrinsic_gas.go index 2bef55de4e1..008986319fe 100644 --- a/execution/protocol/mdgas/intrinsic_gas.go +++ b/execution/protocol/mdgas/intrinsic_gas.go @@ -169,6 +169,15 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) accessListFloorTokens uint64 costPerToken uint64 ) + // EIP-7976 and EIP-7981 share the same per-token rate (16 gas/token), and + // EIP-7981 spec requires EIP-7976 as a precondition. Selecting the rate on + // either flag keeps the access-list floor surcharge (charged at floor rate + // in RegularGas) consistent with the FloorGasCost rate. + if args.IsEIP7976 || args.IsEIP7981 { + costPerToken = params.TxTotalCostFloorPerTokenEIP7976 + } else { + costPerToken = params.TxTotalCostFloorPerToken + } if args.IsEIP7623 && dataLen > 0 { if args.IsEIP7976 { var overflow bool @@ -209,7 +218,7 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) // path so access list data is charged at floor rate regardless of // execution level. if accessListFloorTokens > 0 { - accessListDataGas, overflow := math.SafeMul(accessListFloorTokens, params.TxTotalCostFloorPerTokenEIP7976) + accessListDataGas, overflow := math.SafeMul(accessListFloorTokens, costPerToken) if overflow { return IntrinsicGasCalcResult{}, true } @@ -219,17 +228,6 @@ func CalcIntrinsicGas(args IntrinsicGasCalcArgs) (IntrinsicGasCalcResult, bool) } } } - // EIP-7976 and EIP-7981 share the same per-token rate (16 gas/token), and - // EIP-7981 spec requires EIP-7976 as a precondition. Selecting the rate on - // either flag keeps the access-list floor surcharge (always charged at - // EIP-7976 rate in RegularGas above) consistent with the FloorGasCost rate - // even in the unsupported configuration where EIP-7981 is enabled without - // EIP-7976. - if args.IsEIP7976 || args.IsEIP7981 { - costPerToken = params.TxTotalCostFloorPerTokenEIP7976 - } else { - costPerToken = params.TxTotalCostFloorPerToken - } totalFloorTokens, overflow := math.SafeAdd(calldataFloorTokens, accessListFloorTokens) if overflow { return IntrinsicGasCalcResult{}, true From 1d42a3914f79d580117bd686d4cc1c7ba4224294 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:50:31 +0200 Subject: [PATCH 15/31] update skips --- execution/tests/eest_devnet/block_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/execution/tests/eest_devnet/block_test.go b/execution/tests/eest_devnet/block_test.go index 0d4a707a7f0..754a0541a0a 100644 --- a/execution/tests/eest_devnet/block_test.go +++ b/execution/tests/eest_devnet/block_test.go @@ -64,7 +64,7 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/selfdestruct_to_self_same_tx.json`) // block=1, gas used by execution: 131488, in header: 35024 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_collision_no_log.json`) // block=1, gas used by execution: 131488, in header: 67911 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_insufficient_balance_no_log.json`) // block=1, gas used by execution: 131488, in header: 32226 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_out_of_gas_no_log.json`) // block=1, receiptHash mismatch: fe8fcf8f64b7de17f9d5a53a6f8e3ea6d474c476c5f8b2c3812dc9f424ff8995 != 8b73a12fe0158f68a47b42b3fab00c7956b56e171a9a5bd32433d06ac6756979, headerNum=1, 586674d021629d54faffe1a8262471aa12fd0558c68ba15f34aece9e05f14878 + bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_out_of_gas_no_log.json`) // block=1, receiptHash mismatch: 009438deb1de46992abb88fe0ae9a0ddac9f51e271e187f90e8ee24ba2cb5ad0 != dd8803e13b8cb71811d62735df0b9b37d8c2526eae428f0c655cc8a4c6ff3126, headerNum=1, 64d6033a2a26d14031c313d27413f94c127a47314a50ed3a9150b7091dab3be4 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/failed_create_with_value_no_log.json`) // block=1, receiptHash mismatch: 0287aff45fdc09786733241c6048608415aa4f99b6e873b72be6cd8d861ef799 != 6a84ba66d7c74c68e9eee3ae1f5bdff1983a21b6bb5d4fcac2194132e0c4023a, headerNum=1, f7943b7f9af73920eb176bf0934826a430f25675eecb396660d2f5dbced1d997 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/transfer_with_all_tx_types.json`) // block=1, gas used by execution: 63900, in header: 316980 bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/multiple_refund_types_in_one_tx.json`) // block=1, gas used by execution: 270020, in header: 1584900 @@ -261,7 +261,6 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_precompile_state_access_boundary.json`) // block=1, gas used by execution: 135010, in header: 38869 bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_self.json`) // block=1, gas used by execution: 133836, in header: 35188 bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_system_contract.json`) // block=1, gas used by execution: 152620, in header: 38869 - bt.SkipLoad(`^for_cancun/ported_static/stWalletTest/wallet_execute_over_daily_limit_only_one_owner/wallet_execute_over_daily_limit_only_one_owner.json`) // block=1, gas used by execution: 288804, in header: 131488 bt.Walk(t, dir, func(t *testing.T, name string, test *testutil.BlockTest) { // import pre accounts & construct test genesis block & state root From a273904be30227eb4c8356a03f995523e2c985e1 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Sun, 26 Apr 2026 10:52:18 +0000 Subject: [PATCH 16/31] execution: implement EIP-8037 changes for bal-devnet-4 --- execution/execmodule/exec_module_test.go | 119 ++++++++---- execution/protocol/misc/eip8037.go | 27 ++- execution/protocol/misc/eip8037_test.go | 47 +++++ execution/protocol/params/protocol.go | 1 + execution/protocol/txn_executor.go | 62 ++++++- execution/state/intra_block_state.go | 34 ++++ execution/tests/eest_devnet/block_test.go | 209 ---------------------- execution/vm/evm.go | 75 +++++++- execution/vm/instructions.go | 18 +- execution/vm/operations_acl.go | 5 +- 10 files changed, 322 insertions(+), 275 deletions(-) create mode 100644 execution/protocol/misc/eip8037_test.go diff --git a/execution/execmodule/exec_module_test.go b/execution/execmodule/exec_module_test.go index 5059c2efb60..dbcd51c5f96 100644 --- a/execution/execmodule/exec_module_test.go +++ b/execution/execmodule/exec_module_test.go @@ -1108,10 +1108,12 @@ func drainHeaders(t *testing.T, ch <-chan [][]byte, timeout time.Duration) { // TestAssembleBlockStateGasLimit verifies that the builder respects the EIP-8037 // block validity invariant: gas_used = max(regular, state) <= gas_limit. // -// Contract creations have high intrinsic state gas (~131K per create at -// CostPerStateByte=1174) but low regular gas (~30K). With a 500K gas limit, -// about 4 creates would push state gas past the limit even though regular gas -// has room. Without the fix the builder would produce an invalid block. +// Under EIP-8037's dynamic cost_per_state_byte, ~730 creates (at any +// gas limit) are needed to fill intrinsic state gas via account creation +// alone. To exercise the check in a small number of txns we pad each create +// with large initcode so that code deposit state gas (L × cpsb) dominates. +// With a 120M gas limit (cpsb = 1174) and ~12 KiB initcode each create costs +// ~14.5M state gas, so ~8 fit before state gas trips the block limit. func TestAssembleBlockStateGasLimit(t *testing.T) { t.Parallel() ctx := t.Context() @@ -1122,7 +1124,7 @@ func TestAssembleBlockStateGasLimit(t *testing.T) { genesis := &types.Genesis{ Config: chain.AllProtocolChanges, - GasLimit: 500_000, // low limit so state gas from a few creates exceeds it + GasLimit: 120_000_000, // cpsb = 1174 at this limit Alloc: types.GenesisAlloc{ senderAddr: {Balance: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)}, }, @@ -1144,13 +1146,32 @@ func TestAssembleBlockStateGasLimit(t *testing.T) { require.NoError(t, err) // Submit 10 contract creation txns to the pool. - // Each has ~131K intrinsic state gas but only ~30K regular gas. + // Each deploys a ~12 KiB contract so state gas (code deposit + account + // creation) per create is ~14.5M at cpsb=1174. + // Initcode layout: 12-byte prefix (CODECOPY+RETURN) that deploys the + // `runtimeSize` bytes of JUMPDEST that follow it. + const runtimeSize = 12 * 1024 + initCode := make([]byte, 12+runtimeSize) + initCode[0] = 0x61 // PUSH2 + initCode[1] = byte(runtimeSize >> 8) // size hi + initCode[2] = byte(runtimeSize & 0xff) // size lo + initCode[3] = 0x80 // DUP1 + initCode[4] = 0x60 // PUSH1 + initCode[5] = 0x0c // srcOffset = 12 + initCode[6] = 0x60 // PUSH1 + initCode[7] = 0x00 // dstOffset = 0 + initCode[8] = 0x39 // CODECOPY + initCode[9] = 0x60 // PUSH1 + initCode[10] = 0x00 // retOffset = 0 + initCode[11] = 0xf3 // RETURN + for i := 12; i < len(initCode); i++ { + initCode[i] = 0x5b // JUMPDEST — valid-as-code filler; value doesn't matter for state gas + } baseFee := chainPack.TopBlock.BaseFee().Uint64() - deployCode := []byte{0x60, 0x00} // PUSH1 0x00 — minimal contract rlpTxs := make([][]byte, 10) for i := range rlpTxs { tx, txErr := types.SignTx( - types.NewContractCreation(uint64(i), uint256.NewInt(0), 200_000, uint256.NewInt(baseFee), deployCode), + types.NewContractCreation(uint64(i), uint256.NewInt(0), 16_000_000, uint256.NewInt(baseFee), initCode), *types.LatestSignerForChainID(m.ChainConfig.ChainID), privKey, ) require.NoError(t, txErr) @@ -1198,11 +1219,11 @@ func TestAssembleBlockStateGasLimit(t *testing.T) { // invariant for execution-time state gas (SSTOREs), as opposed to intrinsic // state gas (contract creations tested above). // -// A deployed contract writes 4 new storage slots per call (~150K execution -// state gas, ~41K regular gas, 0 intrinsic state gas). The txpool cannot -// filter these by state gas — only the check inside applyTransaction -// (between ApplyMessage and FinalizeTx) prevents the block from exceeding -// gas_limit. +// A deployed contract writes 4 new storage slots per call. At cpsb=1174 +// (120M gas limit) that costs ~150K execution state gas per call. The +// txpool cannot filter these by state gas — only the check inside +// applyTransaction (between ApplyMessage and FinalizeTx) prevents the +// block from exceeding gas_limit. func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) { t.Parallel() ctx := t.Context() @@ -1213,7 +1234,7 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) { genesis := &types.Genesis{ Config: chain.AllProtocolChanges, - GasLimit: 500_000, + GasLimit: 120_000_000, Alloc: types.GenesisAlloc{ senderAddr: {Balance: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)}, }, @@ -1227,17 +1248,49 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) { exec := m.ExecModule txpool := m.TxPoolGrpcServer - // Deploy a contract whose runtime writes to 4 storage slots per call. - // Runtime: base = calldataload(0); sstore(base+i, 1) for i in 0..3. - deployCode, err := hex.DecodeString( - "601d600c600039601d6000f3" + // initcode: deploy 29-byte runtime - "6000356001815560018160010155600181600201556001816003015500") // runtime - require.NoError(t, err) + // Deploy a contract whose runtime writes to `slotsPerCall` NEW storage + // slots per call. Slot index is base + calldataload(0), so different + // callers/calldata write to non-overlapping regions. At cpsb=1174 each + // slot costs 32 × 1174 = 37,568 state gas. + const slotsPerCall = 100 + // Runtime layout (unrolled): + // PUSH1 0; CALLDATALOAD → stack: [base] + // repeat slotsPerCall times: + // DUP1; PUSH2 i; ADD; PUSH1 1; SWAP1; SSTORE // write 1 at base+i + // STOP + runtime := []byte{0x60, 0x00, 0x35} // PUSH1 0; CALLDATALOAD + for i := 0; i < slotsPerCall; i++ { + runtime = append(runtime, + 0x80, // DUP1 (base) + 0x61, byte(i>>8), byte(i&0xff), // PUSH2 i + 0x01, // ADD + 0x60, 0x01, // PUSH1 1 (value) + 0x90, // SWAP1 (swap value and key) + 0x55, // SSTORE + ) + } + runtime = append(runtime, 0x00) // STOP + runtimeSize := len(runtime) + // Initcode prefix: CODECOPY the runtime into memory then RETURN it. + deployCode := make([]byte, 12+runtimeSize) + deployCode[0] = 0x61 // PUSH2 + deployCode[1] = byte(runtimeSize >> 8) // size hi + deployCode[2] = byte(runtimeSize & 0xff) // size lo + deployCode[3] = 0x80 // DUP1 + deployCode[4] = 0x60 // PUSH1 + deployCode[5] = 0x0c // srcOffset = 12 + deployCode[6] = 0x60 // PUSH1 + deployCode[7] = 0x00 // dstOffset = 0 + deployCode[8] = 0x39 // CODECOPY + deployCode[9] = 0x60 // PUSH1 + deployCode[10] = 0x00 // retOffset = 0 + deployCode[11] = 0xf3 // RETURN + copy(deployCode[12:], runtime) signer := *types.LatestSignerForChainID(m.ChainConfig.ChainID) baseFee := m.Genesis.BaseFee().Uint64() deployTx, err := types.SignTx( - types.NewContractCreation(0, uint256.NewInt(0), 300_000, uint256.NewInt(baseFee), deployCode), + types.NewContractCreation(0, uint256.NewInt(0), 16_000_000, uint256.NewInt(baseFee), deployCode), signer, privKey, ) require.NoError(t, err) @@ -1250,18 +1303,20 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) { err = m.InsertChain(chainPack) require.NoError(t, err) - // Submit 10 call txns. Each writes 4 new slots (~150K state gas, ~41K - // regular gas). With a 500K gas limit, 3 calls fit (~451K state gas) - // but the 4th would push to ~601K. Intrinsic state gas is 0 for all - // calls, so the txpool's regular-gas filter lets them all through — - // the applyTransaction check is the only defense. + // Submit 50 call txns. Each writes 100 new slots (~3.76M state gas at + // cpsb=1174, negligible regular gas compared to state). With a 120M + // gas limit, ~32 calls fit before state gas trips the block limit. + // Intrinsic state gas is 0 for all calls, so the txpool's regular-gas + // filter lets them all through — the applyTransaction check is the + // only defense. + const txCount = 50 baseFee = chainPack.TopBlock.BaseFee().Uint64() - rlpTxs := make([][]byte, 10) + rlpTxs := make([][]byte, txCount) for i := range rlpTxs { var calldata [32]byte - binary.BigEndian.PutUint64(calldata[24:], uint64(i*4)) + binary.BigEndian.PutUint64(calldata[24:], uint64(i*slotsPerCall)) tx, txErr := types.SignTx( - types.NewTransaction(uint64(i+1), contractAddr, uint256.NewInt(0), 300_000, uint256.NewInt(baseFee), calldata[:]), + types.NewTransaction(uint64(i+1), contractAddr, uint256.NewInt(0), 16_000_000, uint256.NewInt(baseFee), calldata[:]), signer, privKey, ) require.NoError(t, txErr) @@ -1291,9 +1346,9 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) { block, err := getAssembledBlock(ctx, exec, payloadId) require.NoError(t, err) - txCount := len(block.Transactions()) - require.Greater(t, txCount, 0, "block should contain at least one tx") - require.Less(t, txCount, 10, "builder should stop before all 10 calls fit") + actualTxCount := len(block.Transactions()) + require.Greater(t, actualTxCount, 0, "block should contain at least one tx") + require.Less(t, actualTxCount, txCount, "builder should stop before all calls fit") // EIP-8037 invariant: gas_used <= gas_limit. require.LessOrEqual(t, block.GasUsed(), block.GasLimit(), diff --git a/execution/protocol/misc/eip8037.go b/execution/protocol/misc/eip8037.go index 5a01dfb6a13..2771b00c59e 100644 --- a/execution/protocol/misc/eip8037.go +++ b/execution/protocol/misc/eip8037.go @@ -17,28 +17,27 @@ package misc import ( - "math" "math/bits" "github.com/erigontech/erigon/execution/protocol/params" ) +// CostPerStateByte derives the per-byte price for new state using the block +// gas limit as per EIP-8037. +// +// raw = ceil((gas_limit * BLOCKS_PER_YEAR) / (2 * TARGET_STATE_GROWTH_PER_YEAR)) +// shifted = raw + CPSB_OFFSET +// shift = max(bit_length(shifted) - CPSB_SIGNIFICANT_BITS, 0) +// quantized = (shifted >> shift) << shift +// cost_per_state_byte = quantized - CPSB_OFFSET, floored at 1 func CostPerStateByte(gasLimit uint64) uint64 { - // TODO this should be removed after bal-devnet-3 (we use hardcoded cspb=1174 for now) - const balDevnet3Spec = true - if balDevnet3Spec { - return 1174 - } - //raw = ceil((gas_limit * 2_628_000) / (2 * TARGET_STATE_GROWTH_PER_YEAR)) - //shifted = raw + CPSB_OFFSET - //shift = max(bit_length(shifted) - CPSB_SIGNIFICANT_BITS, 0) - //cost_per_state_byte = max(((shifted >> shift) << shift) - CPSB_OFFSET, 1) - raw := uint64(math.Ceil(float64(gasLimit*2_628_000) / float64(2*params.TargetStateGrowthPerYear))) + denominator := 2 * params.TargetStateGrowthPerYear + raw := (gasLimit*params.BlocksPerYear + denominator - 1) / denominator shifted := raw + params.CpsbOffset shift := max(bits.Len64(shifted)-params.CpsbSignificantBits, 0) - rounded := (shifted >> shift) << shift - if rounded <= params.CpsbOffset { + quantized := (shifted >> shift) << shift + if quantized <= params.CpsbOffset { return 1 } - return rounded - params.CpsbOffset + return quantized - params.CpsbOffset } diff --git a/execution/protocol/misc/eip8037_test.go b/execution/protocol/misc/eip8037_test.go new file mode 100644 index 00000000000..8b74681a4d6 --- /dev/null +++ b/execution/protocol/misc/eip8037_test.go @@ -0,0 +1,47 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Erigon is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Erigon. If not, see . + +package misc + +import ( + "testing" +) + +func TestCostPerStateByte(t *testing.T) { + // Reference values computed by the EIP-8037 Python reference at devnets/bal/4 + // (see src/ethereum/forks/amsterdam/vm/gas.py::state_gas_per_byte). + tests := []struct { + gasLimit uint64 + want uint64 + }{ + {gasLimit: 1_000_000, want: 1}, + {gasLimit: 30_000_000, want: 150}, + {gasLimit: 36_000_000, want: 150}, + {gasLimit: 60_000_000, want: 662}, + {gasLimit: 100_000_000, want: 1174}, + {gasLimit: 120_000_000, want: 1174}, + {gasLimit: 200_000_000, want: 2198}, + {gasLimit: 300_000_000, want: 3222}, + {gasLimit: 500_000_000, want: 5782}, + {gasLimit: 1_000_000_000, want: 11926}, + } + for _, tt := range tests { + got := CostPerStateByte(tt.gasLimit) + if got != tt.want { + t.Errorf("CostPerStateByte(%d) = %d, want %d", tt.gasLimit, got, tt.want) + } + } +} diff --git a/execution/protocol/params/protocol.go b/execution/protocol/params/protocol.go index 7587dc82ed0..d5bf400598f 100644 --- a/execution/protocol/params/protocol.go +++ b/execution/protocol/params/protocol.go @@ -214,6 +214,7 @@ const ( // EIP-8037: State Creation Gas Cost Increase TargetStateGrowthPerYear uint64 = 107_374_182_400 // 100 × 1024^3 bytes + BlocksPerYear uint64 = 2_628_000 // blocks per year at 12s slot time CpsbOffset = 9_578 // cost_per_state_byte_offset (for quantization) CpsbSignificantBits = 5 // cost_per_state_byte_significant_bits (for quantization) CreateGasEIP8037 = CallValueTransferGas diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index 7e9a5029ad4..a8c66566fc0 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -405,13 +405,15 @@ func (st *TxnExecutor) ApplyFrame() (*evmtypes.ExecutionResult, error) { State: intrinsicGasResult.StateGas, } st.gasRemaining = mdgas.SplitTxnGasLimit(st.msg.Gas(), imdGas, rules) - // EIP-8037 × EIP-7702: authority-exists refund moves from intrinsic state - // gas into the reservoir so execution-time state ops can draw from it. + st.initialGas = st.gasRemaining.Plus(imdGas) + // EIP-8037 × EIP-7702: intrinsic_state_gas is worst-case (assumes all auths + // create new accounts). For each existing-account auth, 112 × cpsb is + // refunded to the reservoir so execution can draw from it; intrinsic_state_gas + // is immutable after validation, so the block-level state gas keeps the + // worst-case value. if stateIgasRefund > 0 && rules.IsAmsterdam { - imdGas.State -= stateIgasRefund st.gasRemaining.State += stateIgasRefund } - st.initialGas = st.gasRemaining.Plus(imdGas) // Execute the preparatory steps for txn execution which includes: // - prepare accessList(post-berlin; eip-7702) @@ -552,11 +554,10 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. State: intrinsicGasResult.StateGas, } st.gasRemaining = mdgas.SplitTxnGasLimit(st.msg.Gas(), imdGas, rules) + st.initialGas = st.gasRemaining.Plus(imdGas) if rules.IsAmsterdam && stateIgasRefund > 0 { - imdGas.State -= stateIgasRefund st.gasRemaining.State += stateIgasRefund } - st.initialGas = st.gasRemaining.Plus(imdGas) if t := st.evm.Config().Tracer; t != nil && t.OnGasChange != nil { t.OnGasChange(st.initialGas.Total(), st.gasRemaining.Total(), tracing.GasChangeTxIntrinsicGas) @@ -610,18 +611,63 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. refundQuotient = params.RefundQuotientEIP3529 } if rules.IsAmsterdam { + // EIP-8037: on a successful tx, refund state gas for accounts + // that were created and self-destructed in the same tx (EIP-6780 + // means no state actually grew). Covers account creation + // (112 × cpsb), non-zero storage writes (32 × cpsb each) and + // the deployed code (len × cpsb). Clamped to the execution + // state gas used so far. + if vmerr == nil { + var stateRefund uint64 + for _, acc := range st.state.SameTxSelfDestructedNewAccounts() { + stateRefund += params.StateBytesNewAccount * st.evm.Context.CostPerStateByte + stateRefund += acc.NonZeroSlots * 32 * st.evm.Context.CostPerStateByte + stateRefund += acc.CodeLen * st.evm.Context.CostPerStateByte + } + if stateRefund > st.evm.StateGasConsumed() { + stateRefund = st.evm.StateGasConsumed() + } + if stateRefund > 0 { + st.evm.RefundTxStateGas(stateRefund) + st.gasRemaining.State += stateRefund + } + } // EIP-8037 + EIP-7778: Block gas accounting uses two dimensions. // stateGasConsumed tracks ALL state gas charges (including spill to regular gas). // regularGasConsumed tracks only regular-dimension opcode gas. - blockState := imdGas.State + st.evm.StateGasConsumed() + // + // EIP-8037: on top-level tx error, execution state gas is refunded + // to the reservoir (state_gas_used := 0). Intrinsic state gas stays + // charged. Block-level state gas therefore collapses to the + // worst-case intrinsic, and the receipt's gas-used drops the + // execution state-gas portion. + executionStateGas := st.evm.StateGasConsumed() + if vmerr != nil { + executionStateGas = 0 + } + blockState := imdGas.State + executionStateGas blockRegular := imdGas.Regular + st.evm.RegularGasConsumed() st.blockRegularGasUsed = max(blockRegular, intrinsicGasResult.FloorGasCost) st.blockStateGasUsed = blockState // Receipt gasUsed: EIP-8037 formula tx.gas - gas_left - reservoir. // Use Total()-level subtraction to avoid per-component uint64 underflow // when gasRemaining.State > initialGas.State (reservoir grew via child reverts). + // RevertedSpillGas is added because handleFrameRevert restores + // the spilled state gas to gas.Regular on top-level REVERT — + // without adding it back here the receipt would under-charge by + // the spill amount. st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + st.evm.RevertedSpillGas() - refund := min(st.txnGasUsedB4Refunds/refundQuotient, st.state.GetRefund().Total()) + if vmerr != nil { + // Top-level error: subtract the execution state gas that is + // refunded to the reservoir (Python spec: + // `state_gas_left += state_gas_used; state_gas_used = 0`). + // This covers both the reservoir-drained and spill portions. + st.txnGasUsedB4Refunds -= st.evm.StateGasConsumed() + } + // EIP-8037: only the regular gas refund flows through the + // 20%-capped refund_counter; state gas refunds have already been + // credited to the reservoir directly via CreditStateGasRefund. + refund := min(st.txnGasUsedB4Refunds/refundQuotient, st.state.GetRefund().Regular) st.txnGasUsed = max(intrinsicGasResult.FloorGasCost, st.txnGasUsedB4Refunds-refund) } else if rules.IsPrague { st.txnGasUsedB4Refunds = st.initialGas.Regular - st.gasRemaining.Regular diff --git a/execution/state/intra_block_state.go b/execution/state/intra_block_state.go index 527512c0582..ffcf7988c64 100644 --- a/execution/state/intra_block_state.go +++ b/execution/state/intra_block_state.go @@ -1438,6 +1438,40 @@ func (sdb *IntraBlockState) IsNewContract(addr accounts.Address) (bool, error) { return !delegated, nil } +// SameTxSelfDestructedNewAccounts returns addresses of contracts that were +// both created and self-destructed during the current transaction, along with +// the number of non-zero storage writes and the deployed-code length for each. +// Used by EIP-8037 to refund the account-creation / storage-set / code-deposit +// state gas charges to the tx at end-of-execution (since no state grew). +func (sdb *IntraBlockState) SameTxSelfDestructedNewAccounts() []SelfDestructedNewAccount { + var result []SelfDestructedNewAccount + for addr, so := range sdb.stateObjects { + if so == nil || !so.selfdestructed || !so.newlyCreated { + continue + } + var nonZeroSlots uint64 + for _, val := range so.dirtyStorage { + if !val.IsZero() { + nonZeroSlots++ + } + } + result = append(result, SelfDestructedNewAccount{ + Address: addr, + NonZeroSlots: nonZeroSlots, + CodeLen: uint64(len(so.code)), + }) + } + return result +} + +// SelfDestructedNewAccount summarises a contract that was created and +// self-destructed in the same transaction, for EIP-8037 refund accounting. +type SelfDestructedNewAccount struct { + Address accounts.Address + NonZeroSlots uint64 + CodeLen uint64 +} + // SetTransientState sets transient storage for a given account. It // adds the change to the journal so that it can be rolled back // to its previous value if there is a revert. diff --git a/execution/tests/eest_devnet/block_test.go b/execution/tests/eest_devnet/block_test.go index 0d4a707a7f0..c25876d8fba 100644 --- a/execution/tests/eest_devnet/block_test.go +++ b/execution/tests/eest_devnet/block_test.go @@ -54,215 +54,6 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { // static — tested in state test format by TestState bt.SkipLoad(`^for_amsterdam/ported_static/`) - // TODO fix failing tests - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/finalization_burn_log_single_account_multiple_transfers.json`) // block=1, gas used by execution: 288804, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/finalization_burn_logs.json`) // block=1, gas used by execution: 652744, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/finalization_burn_logs_multi_account_ordering.json`) // block=1, gas used by execution: 918068, in header: 149961 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/selfdestruct_finalization_after_priority_fee.json`) // block=1, gas used by execution: 265324, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/selfdestruct_same_tx_via_call.json`) // block=1, gas used by execution: 157316, in header: 44709 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/selfdestruct_to_different_address_same_tx.json`) // block=1, gas used by execution: 131488, in header: 37625 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/burn_logs/selfdestruct_to_self_same_tx.json`) // block=1, gas used by execution: 131488, in header: 35024 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_collision_no_log.json`) // block=1, gas used by execution: 131488, in header: 67911 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_insufficient_balance_no_log.json`) // block=1, gas used by execution: 131488, in header: 32226 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/create_out_of_gas_no_log.json`) // block=1, receiptHash mismatch: fe8fcf8f64b7de17f9d5a53a6f8e3ea6d474c476c5f8b2c3812dc9f424ff8995 != 8b73a12fe0158f68a47b42b3fab00c7956b56e171a9a5bd32433d06ac6756979, headerNum=1, 586674d021629d54faffe1a8262471aa12fd0558c68ba15f34aece9e05f14878 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/failed_create_with_value_no_log.json`) // block=1, receiptHash mismatch: 0287aff45fdc09786733241c6048608415aa4f99b6e873b72be6cd8d861ef799 != 6a84ba66d7c74c68e9eee3ae1f5bdff1983a21b6bb5d4fcac2194132e0c4023a, headerNum=1, f7943b7f9af73920eb176bf0934826a430f25675eecb396660d2f5dbced1d997 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/transfer_with_all_tx_types.json`) // block=1, gas used by execution: 63900, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/multiple_refund_types_in_one_tx.json`) // block=1, gas used by execution: 270020, in header: 1584900 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/simple_gas_accounting.json`) // block=1, gas used by execution: 270020, in header: 1584900 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/varying_calldata_costs.json`) // block=1, gas used by execution: 31240, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_aborted_storage_access.json`) // block=1, receiptHash mismatch: 0574ca632811062d8709db6085aef9953a58cc20c1f2a9f2fd58973ee9fc43c5 != 3436c842cadfa64946a5dee36dd6ceaab7cbaab05b7ddcb3e8069b72bcf8620a, headerNum=1, 1efe1b367f53fbddf9e77735a144532932bd5aec9d496bccf4adc5ba0c4fe78f - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists/bal_all_transaction_types.json`) // block=1, gas used by execution: 214842, in header: 346330 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7002/bal_7002_request_invalid.json`) // block=1, gas used by execution: 150272, in header: 56234 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_clear.json`) // block=1, gas used by execution: 57000, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_create.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_delegation_update.json`) // block=1, gas used by execution: 57000, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_double_auth_reset.json`) // block=1, gas used by execution: 54004, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_double_auth_swap.json`) // block=1, gas used by execution: 54004, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_7702_null_address_delegation_no_code_change.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_selfdestruct_to_7702_delegation.json`) // block=1, gas used by execution: 59724, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/bal_withdrawal_to_7702_delegation.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create2_collision.json`) // block=1, receiptHash mismatch: 58c109854ad17a7e6cc36919cc07464496eba4562546763c5719a54dbcf83572 != c0f65f206dc904c838752c6d10e9e70f72efefaefb9f01c5793a730fc4d0ef43, headerNum=1, c650b7b61e4cee14205def343f64864259bcbeb9b127686d448f73dd086cd04b - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_and_oog.json`) // block=1, gas used by execution: 131488, in header: 30023 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_early_failure.json`) // block=1, gas used by execution: 131488, in header: 35026 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_oog_code_deposit.json`) // block=1, receiptHash mismatch: 432aa57d091596e6f080cb880aa2265c360922292588ee56480677ce29c931a1 != fd591027210a4da480975eab0be900db75960133ac29c6e4f479df76fe730315, headerNum=1, f0e3464870d4f197c97ef93cb229adb7fe0898b9333aaeb9da79f3095d8b38e8 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_opcodes/bal_create_selfdestruct_to_self_with_call.json`) // block=1, gas used by execution: 244192, in header: 75136 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip7954_increase_max_contract_size/max_code_size/max_code_size_via_create.json`) // block=1, receiptHash mismatch: 764a45a52f65e30545bc1acec647b68bef0de17812d850a420716c19b6c19f37 != f2baac99015cfaaf4245fccad11e911bf1bba7f51678131b54269bf903cb6316, headerNum=1, 68ec0f21c9fdd7438f527c256bed133b73eb921c5db72957fa0516467b688547 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/dupn/dupn_stack_underflow.json`) // block=1, receiptHash mismatch: 6ebeb82e2fd4ad8ef581ba011ed8590752fbb658e86bb4f29d186cba3f7b1357 != 496ca8c76f744094f9ba323bd2996f088f7609d26fc17a648298dac6203189a0, headerNum=1, a9c78e38550bc1045539456afe1a06c8086c5a708bac1d17b51976ea05acf949 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/exchange/exchange_invalid_immediate_aborts.json`) // block=1, receiptHash mismatch: 6ebeb82e2fd4ad8ef581ba011ed8590752fbb658e86bb4f29d186cba3f7b1357 != 496ca8c76f744094f9ba323bd2996f088f7609d26fc17a648298dac6203189a0, headerNum=1, 3942d6681902bdc203aaac09522f161eca9fcf30c763f74f3307264d99d8f5f9 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/exchange/exchange_stack_underflow.json`) // block=1, receiptHash mismatch: 6ebeb82e2fd4ad8ef581ba011ed8590752fbb658e86bb4f29d186cba3f7b1357 != 496ca8c76f744094f9ba323bd2996f088f7609d26fc17a648298dac6203189a0, headerNum=1, 648868b84582031adf8c7ca3a0b40b14879f1cf2b8c55dc1ad8b57c89f51acf4 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/pc_advancement/dupn_multiple_consecutive_pc_advancement.json`) // block=1, gas used by execution: 112704, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/pc_advancement/dupn_pc_advances_by_2.json`) // block=1, gas used by execution: 112704, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/pc_advancement/exchange_pc_advances_by_2.json`) // block=1, gas used by execution: 112704, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/pc_advancement/mixed_opcodes_pc_advancement.json`) // block=1, gas used by execution: 112704, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/pc_advancement/swapn_pc_advances_by_2.json`) // block=1, gas used by execution: 112704, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/block_2d_gas_accounting/block_2d_gas_boundary_exact_fit.json`) // block=1, gas used by execution: 375680, in header: 460300 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/block_2d_gas_accounting/block_gas_refund_eip7778_no_block_reduction.json`) // block=1, gas used by execution: 112704, in header: 78336 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_value_to_self_destructed_burns_value.json`) // block=1, gas used by execution: 131488, in header: 41864 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_value_to_self_destructed_header_gas_used.json`) // block=1, gas used by execution: 131488, in header: 44465 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_value_to_self_destructed_same_tx_account.json`) // block=1, gas used by execution: 169056, in header: 46874 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/call_zero_value_to_self_destructed_same_tx_account.json`) // block=1, gas used by execution: 131488, in header: 35173 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_call/create_insufficient_balance_returns_reservoir.json`) // block=1, gas used by execution: 169056, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/code_deposit_halt_discards_initcode_state_gas.json`) // block=1, receiptHash mismatch: 6109c57d8983f520f4b510024a1760aff8d920bb293bbc00c30abdac956b3b2f != c7cd454c1e2d8f590ccd8205e653d2649edb81e2ee1259782fb3af690d5648d8, headerNum=1, 89a4abe5fd3c9d1b72b7dc5e1a8aba9fd3164ec844156307f23ceac5b92181a5 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/code_deposit_oog_preserves_parent_reservoir.json`) // block=1, receiptHash mismatch: 87eb1d5efeccbf21568dc8ba422e793d3dc3ca7bea2c064adcd6d4bf89c1f636 != 13caceefb2a53e479514f660e5d50b365ead011387defffccca23e4d8d03c5f4, headerNum=1, 59aa99f406a68008b33cc2807b9bd2988a55798b270b671b549c2923b703baad - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create2_address_collision.json`) // block=1, receiptHash mismatch: ed982053122348c512e18c1ac8827d4e10ef91c2705616cd872dc2ad2a0b03dd != 7637ffd9bbf631be43879032b61a2d3ece5335e64500422749a7ea9001482b68, headerNum=1, c148d12a917d4fc0c5dafc6ffa352ee44d216546824653e3b697b3ac7dbf9ac1 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_child_halt_refunds_state_gas.json`) // block=1, gas used by execution: 1555201, in header: 1536420 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_child_revert_refunds_state_gas.json`) // block=1, gas used by execution: 169056, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_code_deposit_oog_refunds_state_gas.json`) // block=1, gas used by execution: 1555201, in header: 1536420 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_collision_burned_gas_counted_in_block_regular.json`) // block=1, gas used by execution: 131488, in header: 117132 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_collision_refunds_state_gas.json`) // block=1, gas used by execution: 1555201, in header: 1536420 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_mixed_success_and_failure_block_accounting.json`) // block=1, gas used by execution: 262976, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_revert_no_code_deposit_state_gas.json`) // block=1, gas used by execution: 131488, in header: 32232 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/create_silent_failure_refunds_state_gas.json`) // block=1, gas used by execution: 169056, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/failed_create_header_gas_used.json`) // block=1, receiptHash mismatch: 9adb08ce7ee8cdea800360deb4ac97ab74ab228a85c1c3756ad1b8c6b675adf3 != 769baff2fb1b33f057f8234087ba691afc2a7e92d81d7dc023a960fc22378d16, headerNum=1, 5c835fc83a5400f48feeb928f38e7f3dc05966214a9dbeee905b3bc0e6089fd6 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/inner_create_fail_refunds_in_creation_tx.json`) // block=1, gas used by execution: 525952, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/inner_create_succeeds_code_deposit_state_gas.json`) // block=1, gas used by execution: 264150, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/nested_create_code_deposit_cannot_borrow_parent_gas.json`) // block=1, gas used by execution: 131488, in header: 30620 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/nested_create_fail_parent_revert_state_gas.json`) // block=1, receiptHash mismatch: 7bcf94219e25ac0732ab6b16e86421b8ca83921986485c99782a0be5579d3a87 != c55cbaff662c04ece0a9c550a57a05b480590f30131afd89fab8b99a29f685a5, headerNum=1, ea6f529ce05e9a22fe8b59ace8401967a32ff6bfaf0f80a9a3a044b3cdd1cc29 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_create/selfdestruct_in_create_tx_initcode.json`) // block=1, gas used by execution: 262976, in header: 131488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/auth_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 102138, in header: 233626 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/call_new_account_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/cpsb_underflow_boundary.json`) // block=1, gas used by execution: 37568, in header: 26006 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/create_state_gas_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/pricing_at_various_gas_limits.json`) // block=1, gas used by execution: 37568, in header: 381632 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/pricing_changes_with_block_gas_limit.json`) // block=1, gas used by execution: 37568, in header: 26006 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/pricing_minimum_cpsb_floor.json`) // block=1, gas used by execution: 37568, in header: 26006 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/refund_cap_includes_state_gas.json`) // block=1, gas used by execution: 37568, in header: 26112 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/refund_with_reservoir_state_gas.json`) // block=1, gas used by execution: 37568, in header: 26112 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/selfdestruct_new_beneficiary_scales_with_cpsb.json`) // block=1, gas used by execution: 169056, in header: 1717344 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_pricing/sstore_refund_scales_with_cpsb.json`) // block=1, gas used by execution: 37568, in header: 26112 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/creation_tx_failure_preserves_intrinsic_state_gas.json`) // block=1, receiptHash mismatch: 655ba40a763440741644e1cc9308e3161cadd08a84571fb49ff3311789fb61e2 != 34f408ef6c0c284659b1f6f2bb262a45afacfb0ada3448163a7c3bf0520d86ee, headerNum=1, 2ab3e39efd48616245f9037e31450045ef402c07744f16f7d11d674505c802fc - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/creation_tx_regular_check_subtracts_intrinsic_state.json`) // intrinsic gas too low: have 46800, want 161488 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/top_level_failure_refunds_execution_state_gas.json`) // block=1, receiptHash mismatch: 45cbf3c1eacc7b09beaf9eb699a979e045c93d6e3857920c0b33a39b3727d43d != 6109c57d8983f520f4b510024a1760aff8d920bb293bbc00c30abdac956b3b2f, headerNum=1, 6682826169809fc2384bda92bdbe0e432f1d81c87618bc7f72acd977649b6812 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/top_level_failure_refunds_spilled_state_gas.json`) // block=1, gas used by execution: 37568, in header: 26012 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/top_level_failure_refunds_state_gas_propagated_from_child.json`) // block=1, gas used by execution: 37568, in header: 28634 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_reservoir/top_level_failure_zeros_block_state_gas.json`) // block=1, receiptHash mismatch: 45cbf3c1eacc7b09beaf9eb699a979e045c93d6e3857920c0b33a39b3727d43d != 6109c57d8983f520f4b510024a1760aff8d920bb293bbc00c30abdac956b3b2f, headerNum=1, e322f9371ab2c919e6f5252c0c58509f16ae2cf1b41fb0e6db0463823b0eaa14 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_selfdestruct/create_selfdestruct_code_deposit_refund_header_check.json`) // block=1, gas used by execution: 432032, in header: 38088 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_selfdestruct/create_selfdestruct_no_double_refund_with_sstore_restoration.json`) // block=1, gas used by execution: 169056, in header: 40139 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_selfdestruct/create_selfdestruct_refunds_account_and_storage.json`) // block=1, gas used by execution: 319328, in header: 60057 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_selfdestruct/create_selfdestruct_refunds_code_deposit_state_gas.json`) // block=1, gas used by execution: 248888, in header: 38655 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_selfdestruct/selfdestruct_to_self_in_create_tx.json`) // block=1, gas used by execution: 131488, in header: 35027 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_selfdestruct/selfdestruct_via_delegatecall_chain.json`) // block=1, gas used by execution: 247714, in header: 75136 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/auth_refund_block_gas_accounting.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/auth_refund_bypasses_one_fifth_cap.json`) // block=1, gas used by execution: 139706, in header: 271194 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/auth_with_calldata_and_access_list.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/auth_with_multiple_sstores.json`) // block=1, gas used by execution: 214842, in header: 346330 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/authorization_exact_state_gas_boundary.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/authorization_to_precompile_address.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/authorization_with_sstore.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/duplicate_signer_authorizations.json`) // block=1, gas used by execution: 185492, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/existing_account_auth_header_gas_used_uses_worst_case.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/existing_account_refund.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/existing_account_refund_enables_sstore.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/existing_auth_with_reverted_execution_preserves_intrinsic.json`) // block=1, gas used by execution: 64570, in header: 158490 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/many_authorizations_state_gas.json`) // block=1, gas used by execution: 270020, in header: 1584900 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/mixed_auths_header_gas_used_uses_worst_case.json`) // block=1, gas used by execution: 185492, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/mixed_new_and_existing_auths.json`) // block=1, gas used by execution: 54004, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/mixed_valid_and_invalid_auths.json`) // block=1, gas used by execution: 185492, in header: 316980 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/multi_tx_block_auth_refund_and_sstore.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_ancestor_revert.json`) // block=1, gas used by execution: 75136, in header: 78759 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_block_state_gas_zero.json`) // block=1, gas used by execution: 1878400, in header: 276600 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_charge_in_ancestor.json`) // block=1, gas used by execution: 76131, in header: 75136 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_create_init_revert.json`) // block=1, gas used by execution: 75136, in header: 85168 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_create_init_success.json`) // block=1, gas used by execution: 206624, in header: 169056 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_intermediate_values.json`) // block=1, gas used by execution: 37568, in header: 26218 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_mixed_with_genuine_sstore.json`) // block=1, gas used by execution: 75136, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_refund.json`) // block=1, gas used by execution: 37568, in header: 26112 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_reservoir_replenished_inline.json`) // block=1, gas used by execution: 75136, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_reservoir_spillover.json`) // block=1, gas used by execution: 37568, in header: 26112 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_sub_frame_revert.json`) // block=1, gas used by execution: 75136, in header: 76137 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_restoration_then_reset.json`) // block=1, gas used by execution: 75136, in header: 37568 - bt.SkipLoad(`^for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_sstore/sstore_state_gas_all_tx_types.json`) // block=1, gas used by execution: 91572, in header: 354548 - bt.SkipLoad(`^for_amsterdam/berlin/eip2929_gas_cost_increases/create/create_insufficient_balance.json`) // block=1, gas used by execution: 169056, in header: 47918 - bt.SkipLoad(`^for_amsterdam/berlin/eip2929_gas_cost_increases/create/create_nonce_overflow.json`) // block=1, gas used by execution: 169056, in header: 47918 - bt.SkipLoad(`^for_amsterdam/cancun/create/create_oog_from_eoa_refunds/create_oog_from_eoa_refunds.json`) // block=1, receiptHash mismatch: ba9991e573821b0c234e361e68d2fe4b0fc940f8380d61fc18ee6ceb963b7094 != 91e4445a3ed8b70b41017281d14d817b16413ae3d96c8afaabd680ec95ff42d9, headerNum=1, 0d4c5d4b752c49e59f005ae61edeb5fadca9a7609e9d100e14097e7f492b1ff5 - bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_create_contexts/tstore_rollback_on_failed_create.json`) // block=1, receiptHash mismatch: e016160cb1798fde7eabcfd05c6eb41e1a55100f4437aa6196c556af22ec8ce7 != e24f8d65696478f8ffc1cf51d0139830b695891444efba6397cad40130cad1a2, headerNum=1, dba1442a797df47e541242b968d89018c9546b9589f8e113f2743ea635a7460b - bt.SkipLoad(`^for_amsterdam/cancun/eip1153_tstore/tstorage_selfdestruct/reentrant_selfdestructing_call.json`) // block=1, gas used by execution: 335764, in header: 112704 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blobhash_opcode/blobhash_invalid_blob_index.json`) // block=1, gas used by execution: 413248, in header: 88332 - bt.SkipLoad(`^for_amsterdam/cancun/eip4844_blobs/blobhash_opcode/blobhash_scenarios.json`) // block=1, gas used by execution: 450816, in header: 225408 - bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_huge_memory_expansion.json`) // block=1, receiptHash mismatch: 56fe7a0c09434e88468b961fffa5d328dd45d99b0a8d254178c45b1c33ab7a12 != 98ff002f51d977b4e85bd9a688d348d42c26f3b2f31fbfe9f005555adf3e5027, headerNum=1, f5309ec689b572ac095a551de1ab894b6782605b03012a48469f0f961cdd3e94 - bt.SkipLoad(`^for_amsterdam/cancun/eip5656_mcopy/mcopy_memory_expansion/mcopy_memory_expansion.json`) // block=1, receiptHash mismatch: d4ae000564ebd6f4e4df0bcb4524237f62c738385bcb26ef080b2905920cfed5 != 6760bb71c1b6e83941816e9179aa72b55178a33ce8b352e163c13e880e034c71, headerNum=1, 303a329a5ff7b2dfa9676bf4fd95e37db7228714f90508fca5f20b6f434991a8 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/dynamic_create2_selfdestruct_collision/dynamic_create2_selfdestruct_collision.json`) // block=1, gas used by execution: 795972, in header: 473083 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/dynamic_create2_selfdestruct_collision/dynamic_create2_selfdestruct_collision_multi_tx.json`) // block=1, gas used by execution: 570564, in header: 500666 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/dynamic_create2_selfdestruct_collision/dynamic_create2_selfdestruct_collision_two_different_transactions.json`) // block=1, gas used by execution: 896936, in header: 702052 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/calling_from_new_contract_to_pre_existing_contract.json`) // block=1, gas used by execution: 565868, in header: 358070 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/create_and_destroy_multiple_contracts_same_tx.json`) // block=1, gas used by execution: 696182, in header: 282934 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/create_multiple_contracts_destroy_one_then_destroy_other_next_tx.json`) // block=1, gas used by execution: 605784, in header: 378028 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/create_selfdestruct_same_tx.json`) // block=1, gas used by execution: 786580, in header: 545910 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/create_selfdestruct_same_tx_increased_nonce.json`) // block=1, gas used by execution: 1435802, in header: 1171652 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/parent_creates_child_selfdestruct_one.json`) // block=1, gas used by execution: 740794, in header: 535344 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/recreate_self_destructed_contract_different_txs.json`) // block=1, gas used by execution: 500124, in header: 101168 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/recursive_contract_creation_and_selfdestruct.json`) // block=1, gas used by execution: 693834, in header: 488384 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode.json`) // block=1, gas used by execution: 376854, in header: 207798 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode_create_tx.json`) // block=1, gas used by execution: 169056, in header: 131488 - bt.SkipLoad(`^for_amsterdam/cancun/eip6780_selfdestruct/selfdestruct_revert/selfdestruct_created_in_same_tx_with_revert.json`) // block=1, gas used by execution: 612828, in header: 338112 - bt.SkipLoad(`^for_amsterdam/constantinople/eip1014_create2/create2_revert/create2_revert_preserves_balance.json`) // block=1, gas used by execution: 131488, in header: 40175 - bt.SkipLoad(`^for_amsterdam/constantinople/eip1014_create2/create_returndata/create2_return_data.json`) // block=1, gas used by execution: 206624, in header: 78097 - bt.SkipLoad(`^for_amsterdam/constantinople/eip1052_extcodehash/extcodehash/extcodehash_after_selfdestruct.json`) // block=1, gas used by execution: 359244, in header: 225408 - bt.SkipLoad(`^for_amsterdam/constantinople/eip1052_extcodehash/extcodehash/extcodehash_created_and_deleted.json`) // block=1, gas used by execution: 360418, in header: 225408 - bt.SkipLoad(`^for_amsterdam/constantinople/eip1052_extcodehash/extcodehash/extcodehash_created_and_deleted_recheck_outer.json`) // block=1, gas used by execution: 473122, in header: 338112 - bt.SkipLoad(`^for_amsterdam/constantinople/eip1052_extcodehash/extcodehash/extcodehash_subcall_selfdestruct.json`) // block=1, gas used by execution: 474296, in header: 300544 - bt.SkipLoad(`^for_amsterdam/frontier/create/create_deposit_oog/create_deposit_oog.json`) // block=1, gas used by execution: 131488, in header: 70238 - bt.SkipLoad(`^for_amsterdam/frontier/create/create_one_byte/create_one_byte.json`) // block=1, gas used by execution: 43577706, in header: 43446218 - bt.SkipLoad(`^for_amsterdam/frontier/create/create_suicide_during_init/create_suicide_during_transaction_create.json`) // block=1, gas used by execution: 169056, in header: 46953 - bt.SkipLoad(`^for_amsterdam/frontier/create/create_suicide_store/create_suicide_store.json`) // block=1, gas used by execution: 413248, in header: 112704 - bt.SkipLoad(`^for_amsterdam/frontier/opcodes/all_opcodes/cover_revert.json`) // block=1, gas used by execution: 169056, in header: 131488 - bt.SkipLoad(`^for_amsterdam/frontier/scenarios/scenarios/scenarios.json`) // block=21, gas used by execution: 131488, in header: 50495 - bt.SkipLoad(`^for_amsterdam/istanbul/eip1344_chainid/chainid/chainid.json`) // block=1, gas used by execution: 91572, in header: 354548 - bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/collision_selfdestruct/selfdestruct_after_create2_collision.json`) // block=1, gas used by execution: 499019, in header: 409422 - bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/initcollision/init_collision_create_opcode.json`) // block=1, receiptHash mismatch: 524db87016bef6d465f58a76b2d203a5aa7530ed58655fc1a222fc976da98830 != 0ef8df661f25e664b1912098fbf14882312c23e5318ea1af9948df4ad71304eb, headerNum=1, 441aaedd3994f8ef204e166eefeb7cfc63b2250c0423d68737a120ebba7f117e - bt.SkipLoad(`^for_amsterdam/paris/eip7610_create_collision/revert_in_create/create2_collision_storage.json`) // block=1, receiptHash mismatch: c5beba07298d35464a539c2062ecf900c130d2400e66393b762a20377e6af894 != e019c41e16c55b80810e927d1bba60fc149d3da257bd5a4f83260142b1debc8f, headerNum=1, 2c115de7d2427ab2aac6d6d93890448d389878a11e1cb6dec62a5c7ac91e75ab - bt.SkipLoad(`^for_amsterdam/paris/security/selfdestruct_balance_bug/tx_selfdestruct_balance_bug.json`) // block=1, gas used by execution: 266498, in header: 128408 - bt.SkipLoad(`^for_amsterdam/prague/eip6110_deposits/deposits/deposit.json`) // block=1, gas used by execution: 112704, in header: 95374 - bt.SkipLoad(`^for_amsterdam/prague/eip7002_el_triggerable_withdrawals/withdrawal_requests/withdrawal_requests.json`) // block=1, gas used by execution: 225408, in header: 150272 - bt.SkipLoad(`^for_amsterdam/prague/eip7251_consolidations/consolidations/consolidation_requests.json`) // block=1, gas used by execution: 262976, in header: 187840 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/gas/account_warming.json`) // block=1, gas used by execution: 185492, in header: 316980 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/creating_delegation_designation_contract.json`) // block=1, receiptHash mismatch: eb0a2410285068047569b15f5785dacab88b92c4551f532189e41037162268dc != f5610820eb298919377f6d68f2eb097e952382f07a765552fb25d2a0b8bdad59, headerNum=1, c839439445443752f8b49facf7f0eff1f457461e8346d022e9b5683713ffd258 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/delegation_clearing.json`) // block=1, gas used by execution: 102138, in header: 233626 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/delegation_clearing_and_set.json`) // block=1, gas used by execution: 91572, in header: 354548 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/delegation_clearing_failing_tx.json`) // block=1, gas used by execution: 28506, in header: 158490 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/delegation_clearing_tx_to.json`) // block=1, gas used by execution: 28500, in header: 158490 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/eoa_tx_after_set_code.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/ext_code_on_chain_delegating_set_code.json`) // block=1, gas used by execution: 448468, in header: 579956 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/ext_code_on_self_delegating_set_code.json`) // block=1, gas used by execution: 177274, in header: 308762 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/ext_code_on_self_set_code.json`) // block=1, gas used by execution: 177274, in header: 308762 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/ext_code_on_set_code.json`) // block=1, gas used by execution: 177274, in header: 308762 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/many_delegations.json`) // block=1, gas used by execution: 2737768, in header: 15886568 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/nonce_overflow_after_first_authorization.json`) // block=1, gas used by execution: 260628, in header: 392116 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/nonce_validity.json`) // block=1, gas used by execution: 102138, in header: 233626 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/reset_code.json`) // block=1, gas used by execution: 129140, in header: 392116 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/self_code_on_set_code.json`) // block=1, gas used by execution: 139706, in header: 271194 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/self_sponsored_set_code.json`) // block=1, receiptHash mismatch: 64d5287109ecfacbe2249911a87ae7f100a59abe2238541e248a4a1c000f8602 != 6714a2b04db021c4cd8e2e9df55ad5e53d2828b4878d1ae2286b8011f9b032f6, headerNum=1, a0fce57f98c8613a01fa50846ce1e6f05644b726cd9c64d87f55d5070eef0ff5 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_max_depth_call_stack.json`) // block=1, gas used by execution: 257711, in header: 258015 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce.json`) // block=1, gas used by execution: 439076, in header: 1622468 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce_self_sponsored.json`) // block=1, gas used by execution: 307588, in header: 1622468 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_log.json`) // block=1, gas used by execution: 29149, in header: 158490 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_non_empty_storage_non_zero_nonce.json`) // block=1, gas used by execution: 33512, in header: 158490 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_self_destruct.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_self_destructing_account_deployed_in_same_tx.json`) // block=1, gas used by execution: 509516, in header: 308762 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_sstore.json`) // block=1, receiptHash mismatch: da5d7c1fbb98154096d9cb65b43fa5562977b8f344e909589ab32fe0ac29e542 != 3b00e115c1357b094c8e9b40820126ab7984c7cc3603cbc6afbe4ea6b1b3af46, headerNum=1, 279196e38455deb9929c5fb6287da6d58d9109a9b244ce5640761b24f6f6f035 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_sstore_then_sload.json`) // block=1, gas used by execution: 260628, in header: 392116 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/set_code_to_system_contract.json`) // block=1, gas used by execution: 139706, in header: 271194 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/call_pointer_to_created_from_create_after_oog_call_again.json`) // block=1, gas used by execution: 319328, in header: 450816 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/call_to_precompile_in_pointer_context.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/contract_storage_to_pointer_with_storage.json`) // block=1, gas used by execution: 44175, in header: 158490 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/delegation_replacement_call_previous_contract.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/double_auth.json`) // block=1, gas used by execution: 91572, in header: 354548 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_measurements.json`) // block=2, gas used by execution: 646874, in header: 778362 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_normal.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_reentry.json`) // block=1, gas used by execution: 477818, in header: 609306 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_resets_an_empty_code_account_with_storage.json`) // block=1, gas used by execution: 427336, in header: 821800 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_reverts.json`) // block=1, gas used by execution: 177274, in header: 308762 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_to_precompile.json`) // block=1, gas used by execution: 64570, in header: 196058 - bt.SkipLoad(`^for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/pointer_to_static_reentry.json`) // block=1, gas used by execution: 140147, in header: 233626 - bt.SkipLoad(`^for_amsterdam/shanghai/eip3855_push0/push0/push0_contracts.json`) // block=1, receiptHash mismatch: 777f1c1c378807634128348e4f0eeca6a0e7f516ea411690ca04266323f671a4 != 2f13c48591f063e30a792a180116e5ef2611efc62565ec81f9cbe853e23bc631, headerNum=1, a89980a26041d5d56ba7192f619f4dc13d8bed40c41fff9f8be25b15b5a207eb - bt.SkipLoad(`^for_amsterdam/shanghai/eip3860_initcode/initcode/create2_oversized_initcode_with_insufficient_balance.json`) // block=1, gas used by execution: 169056, in header: 37568 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/initcode_selfdestruct_to_self.json`) // block=1, gas used by execution: 131488, in header: 35040 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_state_access_boundary.json`) // block=1, gas used by execution: 157316, in header: 37803 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_precompile_state_access_boundary.json`) // block=1, gas used by execution: 135010, in header: 38869 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_self.json`) // block=1, gas used by execution: 133836, in header: 35188 - bt.SkipLoad(`^for_amsterdam/tangerine_whistle/eip150_operation_gas_costs/eip150_selfdestruct/selfdestruct_to_system_contract.json`) // block=1, gas used by execution: 152620, in header: 38869 - bt.SkipLoad(`^for_cancun/ported_static/stWalletTest/wallet_execute_over_daily_limit_only_one_owner/wallet_execute_over_daily_limit_only_one_owner.json`) // block=1, gas used by execution: 288804, in header: 131488 - bt.Walk(t, dir, func(t *testing.T, name string, test *testutil.BlockTest) { // import pre accounts & construct test genesis block & state root test.ExperimentalBAL = true // TODO eventually remove this from BlockTest and run normally diff --git a/execution/vm/evm.go b/execution/vm/evm.go index eb6e0e394f8..5c6c31c4aa7 100644 --- a/execution/vm/evm.go +++ b/execution/vm/evm.go @@ -98,6 +98,13 @@ type EVM struct { stateGasConsumed uint64 // total state gas charged during tx execution (restored on depth>0 revert, kept on depth-0) regularGasConsumed uint64 // total regular gas charged during tx execution (for block-level accounting) revertedSpillGas uint64 // state gas that spilled to regular and was restored on depth-0 revert + // EIP-8037 state gas refund accumulated across all live frames of this tx. + // Matches python spec's `state_gas_refund`: on child-frame revert we + // subtract the child's contribution (tracked via save/restore around the + // child call) from the reservoir returned to the parent, so the inline + // refund that was credited to the child's reservoir does not leak across + // the revert boundary. + stateGasRefund uint64 } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -174,6 +181,45 @@ func (evm *EVM) ResetGasConsumed() { evm.stateGasConsumed = 0 evm.regularGasConsumed = 0 evm.revertedSpillGas = 0 + evm.stateGasRefund = 0 +} + +// CreditStateGasRefund applies an EIP-8037 state gas refund: credits `amount` +// to the scope's reservoir (like python's `evm.state_gas_left += applied`), +// removes it from block-level state gas accounting, and bumps the tx-level +// refund tracker so that a subsequent frame revert can unwind the inflation. +// +// Unlike regular gas refunds (refund_counter, 20% cap at tx end), state gas +// refunds are returned immediately and uncapped, per the EIP. +func (evm *EVM) CreditStateGasRefund(ctx *CallContext, amount uint64) { + if amount == 0 { + return + } + ctx.stateGas += amount + if amount > evm.stateGasConsumed { + // Defensive clamp. In practice the charge that produced the refund + // must already be in stateGasConsumed, but CALLCODE/DELEGATECALL can + // push the matching charge up to an ancestor that shares storage. + evm.stateGasConsumed = 0 + } else { + evm.stateGasConsumed -= amount + } + evm.stateGasRefund += amount +} + +// RefundTxStateGas reduces the tx-level stateGasConsumed counter. Used by +// end-of-tx refund paths (EIP-6780 same-tx selfdestruct) where there is no +// live frame scope to credit — the tx executor adds the matching amount to +// gasRemaining directly. +func (evm *EVM) RefundTxStateGas(amount uint64) { + if amount == 0 { + return + } + if amount > evm.stateGasConsumed { + evm.stateGasConsumed = 0 + } else { + evm.stateGasConsumed -= amount + } } // handleFrameRevert handles the full error path for a call or create frame: @@ -181,7 +227,7 @@ func (evm *EVM) ResetGasConsumed() { // gas accounting (spill restoration, depth-dependent reservoir preservation). func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, snapshot int, - savedStateGasConsumed, initialChildState uint64) { + savedStateGasConsumed, savedStateGasRefund, initialChildState uint64) { // 1. Revert state changes. evm.intraBlockState.RevertToSnapshot(snapshot, err) @@ -202,12 +248,17 @@ func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, return } childStateConsumed := evm.stateGasConsumed - savedStateGasConsumed - - // For child frames (depth > 0), restore stateGasConsumed so the parent - // frame sees the correct value. At depth 0 there is no parent, and we - // keep the full value for block gas accounting. + // Inline state-gas refunds applied inside the child already inflated + // `gas.State` when they were credited. Unwind that inflation so the + // parent's reservoir reflects only the net-of-refund delta. + childStateGasRefund := evm.stateGasRefund - savedStateGasRefund + + // For child frames (depth > 0), restore stateGasConsumed and the refund + // tracker so the parent frame sees the pre-child value. At depth 0 there + // is no parent and we keep the full value for block gas accounting. if depth > 0 { evm.stateGasConsumed = savedStateGasConsumed + evm.stateGasRefund = savedStateGasRefund } // EIP-8037: "On child revert or exceptional halt, all state gas @@ -235,8 +286,14 @@ func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, } else { // Child frame (depth > 0): restore all consumed state gas // (reservoir-sourced + spill) to the reservoir, preserving any - // sub-child restorations already in the reservoir. + // sub-child restorations already in the reservoir. Subtract the + // child's inline refunds to avoid leaking the bonus into the parent. gas.State += childStateConsumed + if gas.State >= childStateGasRefund { + gas.State -= childStateGasRefund + } else { + gas.State = 0 + } // Regular gas: REVERT preserves it (step 2 doesn't apply); // exceptional halt burns it (step 2 zeroed gas.Regular). } @@ -363,6 +420,7 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts } savedStateGasConsumed := evm.stateGasConsumed + savedStateGasRefund := evm.stateGasRefund initialChildState := gas.State // It is allowed to call precompiles, even via delegatecall @@ -420,7 +478,7 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. if err != nil || evm.config.RestoreState { - evm.handleFrameRevert(&gas, err, depth, snapshot, savedStateGasConsumed, initialChildState) + evm.handleFrameRevert(&gas, err, depth, snapshot, savedStateGasConsumed, savedStateGasRefund, initialChildState) } return ret, gas, err @@ -598,6 +656,7 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem } savedStateGasConsumed := evm.stateGasConsumed + savedStateGasRefund := evm.stateGasRefund initialChildState := gasRemaining.State ret, gasRemaining, err = evm.Run(contract, gasRemaining, nil, false) @@ -665,7 +724,7 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem // above, we revert to the snapshot and consume any gas remaining. Additionally, // when we're in Homestead, this also counts for code storage gas errors. if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { - evm.handleFrameRevert(&gasRemaining, err, depth, snapshot, savedStateGasConsumed, initialChildState) + evm.handleFrameRevert(&gasRemaining, err, depth, snapshot, savedStateGasConsumed, savedStateGasRefund, initialChildState) } return ret, address, gasRemaining, err diff --git a/execution/vm/instructions.go b/execution/vm/instructions.go index b861f88184e..b5012bf5e3b 100644 --- a/execution/vm/instructions.go +++ b/execution/vm/instructions.go @@ -1023,12 +1023,16 @@ func opCreate2(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) // execCreate is the shared implementation for opCreate (salt == nil) and opCreate2 (salt != nil). func execCreate(pc uint64, evm *EVM, scope *CallContext, value uint256.Int, input []byte, salt *uint256.Int) (uint64, []byte, error) { + var accountStateGas uint64 if evm.ChainRules().IsAmsterdam { // EIP-8037: charge state gas for account creation after the static-context // check so that it is not consumed on early failures where no state is - // created (per execution-specs#2608). - stateGas := uint64(params.StateBytesNewAccount) * evm.Context.CostPerStateByte - if !scope.useMdGas(evm, stateGas, mdgas.StateGas, evm.Config().Tracer, tracing.GasChangeIgnored) { + // created (per execution-specs#2608). On any failure (silent + // balance/nonce/depth/collision, child revert, child halt, code deposit + // OOG, oversized code, invalid prefix) the charge is refunded to the + // reservoir below since no account was created. + accountStateGas = uint64(params.StateBytesNewAccount) * evm.Context.CostPerStateByte + if !scope.useMdGas(evm, accountStateGas, mdgas.StateGas, evm.Config().Tracer, tracing.GasChangeIgnored) { return pc, nil, ErrOutOfGas } } @@ -1065,6 +1069,14 @@ func execCreate(pc uint64, evm *EVM, scope *CallContext, value uint256.Int, inpu scope.restoreChildGas(returnGas, evm.config.Tracer) + // EIP-8037: on any CREATE failure no account was created, so refund the + // account-creation state gas to the reservoir and back out of the block + // accounting. The 32*cpsb per-storage-slot and code-deposit charges are + // handled separately (undone by snapshot revert / preDeposit rollback). + if suberr != nil && evm.ChainRules().IsAmsterdam && accountStateGas > 0 { + evm.CreditStateGasRefund(scope, accountStateGas) + } + if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer return pc, res, nil diff --git a/execution/vm/operations_acl.go b/execution/vm/operations_acl.go index f56ccda8b68..4aa0a854124 100644 --- a/execution/vm/operations_acl.go +++ b/execution/vm/operations_acl.go @@ -98,8 +98,11 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // EIP 2200 Original clause: //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) if rules.IsAmsterdam { + // EIP-8037: regular gas refund still flows through the 20%-capped + // refund_counter; state gas refund is uncapped and credited + // directly to the current frame's reservoir. evm.IntraBlockState().AddRefund(params.SstoreSetGasEIP8037 - params.WarmStorageReadCostEIP2929) - evm.IntraBlockState().AddStateRefund(32 * evm.Context.CostPerStateByte) + evm.CreditStateGasRefund(callContext, 32*evm.Context.CostPerStateByte) } else { evm.IntraBlockState().AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929) } From b876ed736f1ca28654e00def1f9e8d6bdb086a7d Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:01:28 +0000 Subject: [PATCH 17/31] execution: implement EIP-8037 changes for bal-devnet-4 --- execution/engineapi/engineapitester/engine_api_tester.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/engineapi/engineapitester/engine_api_tester.go b/execution/engineapi/engineapitester/engine_api_tester.go index 34137cf47e3..d20eed49cca 100644 --- a/execution/engineapi/engineapitester/engine_api_tester.go +++ b/execution/engineapi/engineapitester/engine_api_tester.go @@ -90,7 +90,7 @@ func DefaultEngineApiTesterGenesis(t *testing.T) (*types.Genesis, *ecdsa.Private Config: &chainConfig, Coinbase: coinbaseAddr, Difficulty: merge.ProofOfStakeDifficulty, - GasLimit: 1_000_000_000, + GasLimit: 100_000_000, Alloc: types.GenesisAlloc{ coinbaseAddr: { Balance: new(big.Int).Exp(big.NewInt(10), big.NewInt(21), nil), // 1_000 ETH From 721e444abec02080bff35e6cf238f60d3357cd6e Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:54:18 +0200 Subject: [PATCH 18/31] 8037: go back to static cpsb=1174 --- execution/protocol/misc/eip8037.go | 29 ++++++------ execution/protocol/misc/eip8037_test.go | 59 +++++++++++++------------ 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/execution/protocol/misc/eip8037.go b/execution/protocol/misc/eip8037.go index 2771b00c59e..490a39cd646 100644 --- a/execution/protocol/misc/eip8037.go +++ b/execution/protocol/misc/eip8037.go @@ -16,12 +16,6 @@ package misc -import ( - "math/bits" - - "github.com/erigontech/erigon/execution/protocol/params" -) - // CostPerStateByte derives the per-byte price for new state using the block // gas limit as per EIP-8037. // @@ -31,13 +25,18 @@ import ( // quantized = (shifted >> shift) << shift // cost_per_state_byte = quantized - CPSB_OFFSET, floored at 1 func CostPerStateByte(gasLimit uint64) uint64 { - denominator := 2 * params.TargetStateGrowthPerYear - raw := (gasLimit*params.BlocksPerYear + denominator - 1) / denominator - shifted := raw + params.CpsbOffset - shift := max(bits.Len64(shifted)-params.CpsbSignificantBits, 0) - quantized := (shifted >> shift) << shift - if quantized <= params.CpsbOffset { - return 1 - } - return quantized - params.CpsbOffset + // + // TODO clean up all changes related to the dynamic nature of this. Simplify to static val references. + // + // it was decided to stick to static gas + return 1174 + //denominator := 2 * params.TargetStateGrowthPerYear + //raw := (gasLimit*params.BlocksPerYear + denominator - 1) / denominator + //shifted := raw + params.CpsbOffset + //shift := max(bits.Len64(shifted)-params.CpsbSignificantBits, 0) + //quantized := (shifted >> shift) << shift + //if quantized <= params.CpsbOffset { + // return 1 + //} + //return quantized - params.CpsbOffset } diff --git a/execution/protocol/misc/eip8037_test.go b/execution/protocol/misc/eip8037_test.go index 8b74681a4d6..0a2baa23566 100644 --- a/execution/protocol/misc/eip8037_test.go +++ b/execution/protocol/misc/eip8037_test.go @@ -16,32 +16,33 @@ package misc -import ( - "testing" -) - -func TestCostPerStateByte(t *testing.T) { - // Reference values computed by the EIP-8037 Python reference at devnets/bal/4 - // (see src/ethereum/forks/amsterdam/vm/gas.py::state_gas_per_byte). - tests := []struct { - gasLimit uint64 - want uint64 - }{ - {gasLimit: 1_000_000, want: 1}, - {gasLimit: 30_000_000, want: 150}, - {gasLimit: 36_000_000, want: 150}, - {gasLimit: 60_000_000, want: 662}, - {gasLimit: 100_000_000, want: 1174}, - {gasLimit: 120_000_000, want: 1174}, - {gasLimit: 200_000_000, want: 2198}, - {gasLimit: 300_000_000, want: 3222}, - {gasLimit: 500_000_000, want: 5782}, - {gasLimit: 1_000_000_000, want: 11926}, - } - for _, tt := range tests { - got := CostPerStateByte(tt.gasLimit) - if got != tt.want { - t.Errorf("CostPerStateByte(%d) = %d, want %d", tt.gasLimit, got, tt.want) - } - } -} +// +//import ( +// "testing" +//) +// +//func TestCostPerStateByte(t *testing.T) { +// // Reference values computed by the EIP-8037 Python reference at devnets/bal/4 +// // (see src/ethereum/forks/amsterdam/vm/gas.py::state_gas_per_byte). +// tests := []struct { +// gasLimit uint64 +// want uint64 +// }{ +// {gasLimit: 1_000_000, want: 1}, +// {gasLimit: 30_000_000, want: 150}, +// {gasLimit: 36_000_000, want: 150}, +// {gasLimit: 60_000_000, want: 662}, +// {gasLimit: 100_000_000, want: 1174}, +// {gasLimit: 120_000_000, want: 1174}, +// {gasLimit: 200_000_000, want: 2198}, +// {gasLimit: 300_000_000, want: 3222}, +// {gasLimit: 500_000_000, want: 5782}, +// {gasLimit: 1_000_000_000, want: 11926}, +// } +// for _, tt := range tests { +// got := CostPerStateByte(tt.gasLimit) +// if got != tt.want { +// t.Errorf("CostPerStateByte(%d) = %d, want %d", tt.gasLimit, got, tt.want) +// } +// } +//} From 1e6b7d28d5be3aac5abe98426b18425925d2cb7d Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:05:42 +0000 Subject: [PATCH 19/31] update fixtures to snobal-devnet-4 --- execution/tests/execution-spec-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/tests/execution-spec-tests b/execution/tests/execution-spec-tests index ec66995f0de..6c2af7fc95d 160000 --- a/execution/tests/execution-spec-tests +++ b/execution/tests/execution-spec-tests @@ -1 +1 @@ -Subproject commit ec66995f0debb19fb1e0e4499cf7e4a172b46028 +Subproject commit 6c2af7fc95d1c0aa781898b1a7ad78769a536d7f From e771b512f170d0194b90fb13319dc9a079647c89 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:47:09 +0000 Subject: [PATCH 20/31] plan ready --- ...20260427-eip8037-journal-state-gas-spec.md | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 docs/plans/20260427-eip8037-journal-state-gas-spec.md diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md new file mode 100644 index 00000000000..b852e8d1cda --- /dev/null +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -0,0 +1,328 @@ +# EIP-8037 Journal-Walk State-Gas Accounting (bal-devnet-4 v2) + +## Context + +EIP-8037 (Amsterdam) introduced multi-dimensional gas: a regular-gas dimension and a state-gas dimension that prices state growth. The current implementation charges state gas eagerly at every state-touching opcode (SSTORE, CREATE, CALL-to-empty, SELFDESTRUCT, code deposit) and unwinds those charges via ad-hoc refund rules: + +- SSTORE 0→X→0 inline credit via `evm.CreditStateGasRefund`, with cross-frame plumbing through `state_gas_refund_pending` for DELEGATECALL/CALLCODE chains (PR #2733). +- CREATE silent-failure refunds of `GAS_NEW_ACCOUNT` on insufficient balance, nonce overflow, depth, and address collision (PR #2704). +- Code-deposit halt unwind that rolls back initcode state-gas charges (PR #2595). +- Same-tx SELFDESTRUCT refund driven by `IntraBlockState.SameTxSelfDestructedNewAccounts()` at tx finalize. +- Frame revert/halt accounting in `evm.handleFrameRevert` that restores spill, refund-tracker, and reservoir at depth-dependent boundaries. + +This sprawl has produced bugs the snøbal-devnet-4 test suite has surfaced, and there is concern that more cases are lurking. The new design eliminates per-opcode state-gas charging entirely. State gas is computed once per call/create frame at commit time by walking the journal segment that frame produced. On revert/halt the journal undoes everything → no state gas charged for that frame. All historical refund paths collapse into one rule: each frame's net contribution is `walk_total − committed_children_total`; charge if positive, credit if negative. + +## Design Decisions (confirmed) + +1. **Charge timing**: pure frame-end. Opcodes charge regular gas only. +2. **Reservoir model**: keep `mdgas.MdGas{Regular, State}`, `SplitTxnGasLimit`, full-reservoir-pass-to-child, and spill-into-`gas_left` when reservoir exhausted. +3. **Per-frame delta accounting**: each frame at commit walks its journal segment and computes `delta = walk_total − committed_children_total`. If `delta > 0`, charge `delta × cpsb` from the frame's reservoir (with spill into `gas_left`; OOG → revert). If `delta < 0`, credit `|delta| × cpsb` back to the frame's reservoir AND decrement `evm.executionStateGas`. This single mechanism handles every cross-frame interaction (SSTORE 0→X→0 across siblings/DELEGATECALL chains, create-then-destroy across frames, etc.) without any refund plumbing. The walk uses **`storageChange.originalValue`** (= tx-entry value, captured at SetState time) as the "new slot" determinant, matching spec PR 11573's rule and ensuring OOG attribution at the spec-correct frame. +4. **SELFDESTRUCT filter applied only at the top-frame walk**: sub-frames walk *without* the `.newlyCreated && .selfdestructed` filter (so EIP-6780 net-0 cases temporarily over-count and OOG mid-execution exactly as the spec wants — state IS allocated transiently). At the top frame's commit (depth==0), the walk DOES apply the filter, which makes the top-frame walk `< committed_children_total`, producing the negative delta that credits the over-charge back. Multiple selfdestructs of the same account are deduplicated naturally because the walk keys on address (first occurrence per address). +5. **Top-level CREATE tx**: pass `excludeAddr` so the contract address (already covered by the 112×cpsb intrinsic) is not double-counted by the top-frame walk. Snapshot stays where it is; the top-level `createObjectChange` is just skipped in the walk. The exclusion is derived in-place from `depth == 0` inside `evm.create()` (where the contract address is already a local parameter) — no coordination from TxnExecutor needed, no field on the EVM struct. +6. **Per-depth `committedChildBytes` accumulator on the EVM**, populated by each child as it commits. No journal-range bookkeeping (`frameChildRanges` is unnecessary because the running total of children's contributions, combined with the parent's full-segment walk and credit-on-negative-delta, is strictly more correct than range exclusion — the latter would miss cross-frame SSTORE 0→X→0). + +## Algorithm + +At each call/create frame: + +``` +Frame entry (evm.call / evm.create): + frameStart = ibs.JournalLength() + push 0 onto evm.committedChildBytes // per-depth accumulator + +Frame body runs (regular gas charged inline as today; no state-gas charges). + +Frame commit (success path, IsAmsterdam, chargeStateGas): + frameEnd = ibs.JournalLength() + applyFilt = (depth == 0) // filter only at top frame + excludeC = address if (in evm.create() && depth == 0) else NilAddress + // address is the local param of evm.create(); + // for evm.call() it's always NilAddress + walkTotal = ibs.ComputeFrameStateBytes(frameStart, frameEnd, applyFilt, excludeC) + childTotal = evm.committedChildBytes[depth] + delta = int64(walkTotal) - int64(childTotal) + + if delta > 0: + stateGas = uint64(delta) * cpsb + deduct stateGas from gas.State first, spill into gas.Regular + if insufficient → err = ErrOutOfGas (then handleFrameRevert path) + else evm.executionStateGas += stateGas + elif delta < 0: + creditGas = uint64(-delta) * cpsb + gas.State += creditGas // credit current frame + evm.executionStateGas -= creditGas // shrink tx total + // delta == 0 → nothing to do + + pop evm.committedChildBytes[depth] + if depth > 0: + evm.committedChildBytes[depth-1] += walkTotal // propagate to parent + +Frame revert/halt: + RevertToSnapshot // journal undoes everything + burn regular gas on exceptional halt (same as today) + pop evm.committedChildBytes[depth] // do NOT add to parent + on top-level revert (depth==0, err != nil): + evm.executionStateGas = 0 + +Tx finalize (TxnExecutor.Execute): + blockStateGas = intrinsicStateGas + evm.executionStateGas + // No refund logic in TxnExecutor — credits/charges already baked in. +``` + +`ComputeFrameStateBytes(start, end, applyFilter, excludeCreate)` walks `journal.entries[start:end]` linearly (no range exclusions). It accumulates: + +- **Account creations** — `createObjectChange` and `resetObjectChange`, first occurrence per address. Skip if `excludeCreate == account`. Skip if stateObject is nil. If `applyFilter` AND stateObject is `.newlyCreated && .selfdestructed`, skip. Otherwise +112. +- **Code deposit** — `codeChange`, first per address. If `applyFilter` AND `.newlyCreated && .selfdestructed`, skip. When `prevhash` is empty AND current `stateObject.code` is non-empty, +`len(code)`. +- **Storage 0→non-zero** — `storageChange`, dedup by `(address, key)`. If `applyFilter` AND `.newlyCreated && .selfdestructed`, skip. **Use the entry's `originalValue` field (= tx-entry value, captured at SetState time via `GetCommittedState`).** When `originalValue.IsZero()` AND current value (`stateObject.GetState(key)`) is non-zero, +32. +- All other entry types (`selfdestructChange`, `balanceChange`, `nonceChange`, `refundChange`, `addLogChange`, `transientStorageChange`, `accessList*Change`, `touchAccount`, `balanceIncrease*`, `fakeStorageChange`) are skipped. + +**Why `originalValue` (tx-entry) instead of first-prev-in-segment**: matches spec PR 11573's "new slot" rule, which references the slot's tx-entry value (not frame-entry). Without this, a parent that wrote 0→X and then a child that wrote X→Y would over-charge the parent and under-charge the child relative to the spec, producing the same tx-level total but different per-frame OOG attribution. With `originalValue`, the deepest committed observer that sees `originalValue=0 AND current!=0` is the one that charges, and OOG fires at the spec-correct frame. + +## Why this resolves the existing edge cases + +| Old edge case | New behavior | +|---|---| +| SSTORE 0→X→0 same frame | Walk: `originalValue=0 AND current=0` → 0 bytes. No refund needed. | +| SSTORE 0→X→0 across DELEGATECALL/CALLCODE chains, or sibling frames | Creator frame sees `originalValue=0 AND current=non-zero` at its commit time → +32 charge. Clearer frame and parent see `originalValue=0 AND current=0` → 0. Parent's `committedChildBytes` includes the creator's +32, parent's walk yields 0 → parent.delta=−32 → credit. `state_gas_refund_pending` plumbing deleted entirely. | +| CREATE silent failure (collision, balance, depth, nonce) | No journal entries produced (snapshot pushed/popped) → 0 bytes for that frame. No `CreditStateGasRefund` needed. | +| Code-deposit halt | `SetCode` not called when deposit OOGs → no `codeChange` in journal → 0 code bytes. Plus regular-gas rollback (still needed). | +| Same-tx SELFDESTRUCT (within or cross frame, single or multiple times) | Sub-frames walk without filter → may charge transiently (spec-correct: state was real during execution). At top-frame commit the filter applies, making top.walk < committed_children_total → negative top.delta → credit. Per-address dedup is automatic (walk keys on address; multiple selfdestructs of same account → 1 skip). | +| Frame OOG state-gas restoration to parent | Frame revert → journal undoes → no commit-time charge fired. Nothing to restore. | +| Top-level revert state-gas restoration | `evm.executionStateGas = 0` on top-level revert. Tx-state-gas = intrinsic only. | +| Per-frame OOG attribution matches spec | `originalValue` (tx-entry) drives "new slot" check; the deepest committed observer with `originalValue=0 AND current!=0` charges and OOGs, matching spec PR 11573. | + +## Alignment with EIP-8037 PR 11573 + +PR [ethereum/EIPs#11573](https://github.com/ethereum/EIPs/pull/11573) ("Update EIP-8037: fixed CPSB + frame accounting") rewrites EIP-8037 to drop per-opcode charging in favour of frame-end accounting. Our plan is consistent with the PR's intent and produces matching tx-level totals, but the per-frame mechanism differs. + +### Where we match +- **Constant CPSB = 1174** (PR's quantization-removed table). Already hardcoded in `eip8037.go`. +- **Frame-end charging only** — opcodes do not emit state-gas charges. +- **Reverted/halted frames produce no debits or credits.** +- **SELFDESTRUCT accounting deferred to top-frame commit** (spec calls it "transaction end"; for us this is `evm.create()` / `evm.call()` at `depth==0`, which is the same moment). +- **Each created/cleared slot or account accounted for exactly once across the call stack** (we get this property automatically from the first-prevalue-in-segment rule + `committedChildBytes` delta math). +- All previously-needed refund machinery (SSTORE 0→X→0 inline credit, CREATE silent-failure refund, code-deposit halt unwind, same-tx SELFDESTRUCT refund at TxnExecutor) is removed in both designs. + +### Where the mechanism differs +The spec emits per-slot charges and refunds at every frame commit by examining three reference values (`tx-entry`, `frame-entry`, `frame-exit`). Our plan emits per-frame *deltas* (charge or credit) where the walk uses `tx-entry` (via `storageChange.originalValue`) as the new-slot determinant, combined with `committedChildBytes` accumulator + delta math. + +The two approaches produce the same per-frame charge attribution for the **new slot** rule (which is the rule that matters for OOG), and the same tx-level totals for every scenario. + +What we don't emit explicitly: +- The spec's **"cleared slot, zero at tx start" refund** (`frame-entry != 0 AND frame-exit == 0 AND tx-entry == 0`) is not emitted as a separate refund step. In our plan, the slot simply isn't charged in the first place (the walk's new-slot check requires `current != 0`), so there's nothing to refund. The spec's literal text reads "Refund STATE_BYTES_PER_STORAGE_SET × CPSB" which would create gas without a prior charge in some traces — we read this as the spec intending a matched-pair (refund cancels a charge), so omitting both keeps the net total correct. + +### OOG attribution +With `originalValue` driving the new-slot rule, our plan attributes the per-frame charge to the same frame the spec does — the deepest committed observer with `originalValue=0 AND frame-exit!=0`. Tight-reservoir OOG fires at the spec-correct frame. No consensus divergence on storage-slot OOG. + +For **accounts and code**, no equivalent tx-entry field is needed because the EVM's existing collision rule (`evm.create()` checks `nonce != 0 || !contractHash.IsEmpty() || hasStorage`) ensures at most one `createObjectChange` (or `resetObjectChange`) and at most one main-frame `codeChange` per address per tx: +- Pre-existing accounts (EIP-161 prunes empties) collide on CREATE. +- Newly-created → SELFDESTRUCT → re-CREATE is blocked: SELFDESTRUCT leaves nonce/code untouched until tx finalize, so the second CREATE collides on `nonce != 0`. +- EIP-7702 auth-list `codeChange` happens before the top-frame snapshot (intrinsic processing) so it's outside every frame's walk segment. + +So the walk's "first createObjectChange/codeChange per address" rule is spec-correct without any tx-entry comparison. No remaining divergence. + +## Edge cases (worked traces) + +The "originalValue (tx-entry) + current-value-at-commit" rule, combined with the per-frame `delta = walk − committedChildBytes` charge/credit, handles these without any opcode-level refund plumbing. All scenarios assume EIP-8037 (`IsAmsterdam`) and `cpsb = cost_per_state_byte`. Since `storageChange.originalValue` is the slot's value at tx start (captured via `GetCommittedState`), pre-S=X scenarios short-circuit to 0 bytes immediately — the slot was already non-zero at tx start, so no rule can identify it as "new state". + +### Storage-slot scenarios + +For each scenario: `S` is a single storage slot on some address. Pre-tx-committed value is stated. Frame nesting is given as `T → F` (T calls F as child). Siblings means the parent calls them in sequence. + +| # | Scenario | Per-frame charges/credits | Net executionStateGas | +|---|---|---|---| +| 1 | Pre-S=0; F: 0→X→0 (same frame) | F.walk: originalValue=0, curr=0 → 0; F.delta=0 | 0 | +| 2 | Pre-S=0; T → F1 (0→X) → F2 (X→0) | F2: originalValue=0, curr=0 → 0; F1.walk: originalValue=0, curr=0 → 0; T: 0 | 0 | +| 3 | Pre-S=X; T → F1 (X→0) → F2 (0→Y) | originalValue=X non-zero everywhere → 0 bytes at every frame; no charges or credits | 0 | +| 4 | Pre-S=0; T → F1 (0→X) → F2 (X→Y) | F2.walk: originalValue=0, curr=Y → +32 charge (deepest observer); F1.walk: originalValue=0, curr=Y → +32, committedChildBytes=32, delta=0; T: same, delta=0 | 32 (charged at F2, matches spec) | +| 5 | Pre-S=X; T → F1 (X→Y) → F2 (Y→Z) | originalValue=X non-zero → 0 everywhere | 0 | +| 6 | Pre-S=0; T → F1 (0→X) (siblings) F2 (X→0) F3 (0→Y) | F1: originalValue=0, curr=X → +32 charge; F2: originalValue=0, curr=0 → 0; F3: originalValue=0, curr=Y → +32 charge; T.committedChildBytes=64; T.walk: originalValue=0, curr=Y → +32; delta=32−64=−32 credit | 32 | +| 7 | Pre-S=X; T → F1 (X→0) → F2 (0→Y) → F3 (Y→Z) | originalValue=X non-zero → 0 everywhere | 0 | +| 8 | Pre-S=0; F1 (0→X), F2 sibling reverts after starting an SSTORE | F2 reverts → its journal entry gone; F1: originalValue=0, curr=X → +32 charge; T: same, delta=32−32=0 | 32 | +| 9 | Pre-S=X; T → DELEGATECALL F1 (X→0) → DELEGATECALL F2 (0→0)¹ | F2: noop (no journal entry²); F1: originalValue=X, curr=0 → 0; T: same → 0 | 0 | +| 10 | Pre-S=0; T → DELEGATECALL chain F1→F2→F3, each does 0→X then X→0 across the chain | originalValue=0, curr at each frame's commit reflects intermediate state; net 0 cancels at top | 0 | +| 11 | Pre-S=0; T → A_1 (0→X) → A_2 child (X→0) → A_3 grandchild (0→Y) | A_3.walk: originalValue=0, curr=Y → +32 charge (deepest observer); A_2.walk: originalValue=0, curr=Y → +32, committedChildBytes=32, delta=0; A_1.walk: originalValue=0, curr=Y → +32, committedChildBytes=32, delta=0; T: same, delta=0 | 32 (charged once at A_3, matches spec) | +| 12 | Pre-S=0; same as #11 but A_3 reverts | A_3 journal entries gone; A_2/A_1: originalValue=0, curr=0 (S reverted to 0) → 0; T: 0 | 0 | +| 13 | Pre-S=X; T → A_1 (X→0) → A_2 (0→Y) → A_3 (Y→0) | originalValue=X non-zero → 0 everywhere | 0 (slot deletes back to 0; no new state) | +| 14 | Pre-S=0; T calls F1 (0→X), F2 (X→0), F3 (0→Y) as **sequential siblings** | F1: +32 charge; F2: originalValue=0, curr=0 → 0; F3: +32 charge; T.committedChildBytes=64; T.walk: originalValue=0, curr=Y → +32; delta=32−64=−32 credit | 32 (slot ends at Y from 0) | +| 15 | Pre-S=0; SSTOREs by deepest frames under **different parents**: T→F1→F1a (0→X); T→F2→F2a (X→0); T→F3→F3a (0→Y) | F1a: +32 charge; F2a: 0; F3a: +32 charge; F1/F2/F3 walks see same originalValue=0/curr → +32/0/+32, but committedChildBytes covers them so delta=0/0/0; T.committedChildBytes=64; T.walk: originalValue=0, curr=Y → +32; delta=−32 credit | 32 | +| 16 | Pre-S=0; **mixed nesting + siblings**: T→F1 (F1 SSTORE 0→X then F1→F1a does X→0; F1 commits); T→F2 (does 0→Y) | F1a: originalValue=0, curr=0 → 0; F1.walk: originalValue=0, curr=0 (F1a cleared) → 0, delta=0; F2: +32 charge; T: originalValue=0, curr=Y → +32, delta=32−32=0 | 32 | +| 17 | Pre-S=0; **sibling cancellation + parent direct write**: T→F1 (0→X), T→F2 (X→0), T→F3 (0→Y), then T directly SSTORE Y→Z | F1+32, F2:0, F3+32; T.walk: originalValue=0, curr=Z → +32; T.committedChildBytes=64; delta=−32 credit | 32 (slot ends at Z from 0) | +| 18 | Pre-S=0; **same-tree across siblings**: T→F1 (which has F1a doing 0→X and F1b doing X→0 as siblings under F1); T→F2 (does 0→Y) | F1a: +32 charge; F1b: 0; F1.walk: originalValue=0, curr=0 → 0, delta=0−32=−32 credit; F2: +32 charge; T: originalValue=0, curr=Y → +32, delta=32−(0+32)=0 | 32 | + +#### Case A: pre-set slot ending cleared (pre-S=X, final=0). All net **0** state gas — slot is being deleted, no new state. + +With `originalValue=X` (non-zero) on every storageChange entry for S, the new-slot rule never triggers (it requires `originalValue.IsZero()`). Every frame's walk yields 0 bytes for S. No charges, no credits, no per-frame delta. Total: **0** for all variants below. + +| # | Scenario | Trace | +|---|---|---| +| 19 | Pre-S=X; **sequential siblings**: T→F1 (X→0), F2 (0→Y), F3 (Y→0) | originalValue=X non-zero → 0 everywhere | +| 20 | Pre-S=X; **different parents**: T→F1→F1a (X→0); T→F2→F2a (0→Y); T→F3→F3a (Y→0) | originalValue=X non-zero → 0 everywhere | +| 21 | Pre-S=X; **mixed nesting+siblings**: T→F1 (F1: X→0 then F1→F1a does 0→Y; F1 commits); T→F2 SSTORE Y→0 | originalValue=X non-zero → 0 everywhere | +| 22 | Pre-S=X; **same-tree across siblings**: T→F1 (which has F1a:X→0 and F1b:0→Y); T→F2 (Y→0) | originalValue=X non-zero → 0 everywhere | + +#### Case B: pre-set slot ending still-set (pre-S=X, final=Y non-zero). All net **0** state gas — slot was already non-zero, no new state. + +Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new-slot rule. + +| # | Scenario | Trace | +|---|---|---| +| 23 | Pre-S=X; **3-deep nested**: T→A_1 (X→Y') →A_2 child (Y'→Z') →A_3 grandchild (Z'→Y) | originalValue=X non-zero → 0 everywhere | +| 24 | Pre-S=X; **sequential siblings**: T→F1 (X→Y'), F2 (Y'→0), F3 (0→Y) | originalValue=X non-zero → 0 everywhere | +| 25 | Pre-S=X; **different parents**: T→F1→F1a (X→Y'); T→F2→F2a (Y'→0); T→F3→F3a (0→Y) | originalValue=X non-zero → 0 everywhere | +| 26 | Pre-S=X; **mixed nesting+siblings**: T→F1 (F1: X→Y' then F1→F1a does Y'→Z'; F1 commits); T→F2 SSTORE Z'→Y | originalValue=X non-zero → 0 everywhere | +| 27 | Pre-S=X; **same-tree across siblings**: T→F1 (F1a:X→Y', F1b:Y'→0); T→F2 (0→Y) | originalValue=X non-zero → 0 everywhere | + +¹ DELEGATECALL/CALLCODE write to caller's storage, so `storageChange.account` keys to the storage owner — same dedup rules apply. +² A 0→0 SSTORE is a no-op and emits no journal entry (existing IBS behavior). + +### Account-creation / SELFDESTRUCT scenarios + +| # | Scenario | Per-frame charges/credits | Net executionStateGas | +|---|---|---|---| +| 28 | F creates account X with no code, no slots; F commits | F.walk: createObjectChange{X} → +112 charge; T (filter): X exists, not selfdestructed → +112, delta=0 | 112 | +| 29 | F creates X with code length L and N non-zero slots; F commits | F.walk: 112 + L + 32N charge; T (filter): same, delta=0 | 112 + L + 32N | +| 30 | F creates X then SELFDESTRUCTs X (same frame) | F.walk (no filter): 112+L+32N charge; T.walk (filter applied, X is `.newlyCreated && .selfdestructed`) → 0; delta=−(112+L+32N) credit | 0 | +| 31 | T → F1 creates X; T → F2 (sibling) SELFDESTRUCTs X | F1 charges 112 (or 112+L+32N); F2: 0; T (filter): X skipped → 0; delta=−(112+...) credit | 0 | +| 32 | T → F1 creates X; T → F1 then SELFDESTRUCTs X (creator destroys) | F1.walk (no filter): 112+... charge; T (filter): X skipped → 0, credit | 0 | +| 33 | T → F1 → F2 creates X; F1 SELFDESTRUCTs X (parent destroys child's creation) | F2 charges 112+...; F1.walk (no filter): 112+... (its own segment includes F2's createObjectChange), delta=112+...−(112+...)=0; T (filter): 0, delta=−(112+...) credit | 0 | +| 34 | Multiple SELFDESTRUCT of same newly-created X (e.g. F1 creates X; F2, F3, F4 each SELFDESTRUCT X) | F1 charges 112+...; F2/F3/F4: 0 each; T.walk (filter): X skipped exactly once (per-address dedup); credit fires once | 0 | + +#### Cross-frame CREATE+SELFDESTRUCT variants (mirror of storage cases #11–#18) + +| # | Scenario | Per-frame charges/credits | Net | +|---|---|---|---| +| 35 | **3-deep nested**: T→F1→F1a creates X; F1a→F1ab SELFDESTRUCTs X | F1ab: selfdestructChange (skip) → 0; F1a.walk: createObjectChange (no filter), selfdestructChange (skip) → +112 charge; F1.walk: same → +112, delta=112−112=0; T.committedChildBytes=112; T (filter): X skipped → 0; delta=−112 credit | 0 | +| 36 | **Different parents**: T→F1→F1a creates X; T→F2→F2a SELFDESTRUCTs X | F1a: +112 charge; F1.walk: +112, delta=0; F2a: 0; F2: 0; T.committedChildBytes=112; T (filter): X skipped → 0; delta=−112 credit | 0 | +| 37 | **Mixed nesting+siblings**: T→F1 (F1 creates X then F1→F1a SELFDESTRUCTs X; F1 commits); T→F2 (does something else not touching X) | F1a: 0; F1.walk: createObjectChange (its own), selfdestructChange (F1a's) → +112 charge; F2: 0; T (filter): X skipped → 0; T.delta=−112 credit | 0 | +| 38 | **Same-tree across siblings**: T→F1 (F1a creates X, F1b SELFDESTRUCTs X as siblings under F1); T→F2 (no-op for X) | F1a: +112 charge; F1b: 0; F1.walk: createObjectChange (F1a's), selfdestructChange (F1b's) → +112, delta=112−112=0; F2: 0; T (filter): X skipped → 0; delta=−112 credit | 0 | +| 39 | **Creator nested, destroyer at top-level sibling**: T→F1→F1a creates X; F1 returns to T; T→F2 SELFDESTRUCTs X | F1a: +112 charge; F1.walk: +112, delta=0, totalForParent=112; F2: 0; T.committedChildBytes=112; T (filter): X skipped → 0; delta=−112 credit | 0 | +| 40 | **Spread destroys**: T→F1 creates X; T→F2 SELFDESTRUCTs X; T→F3 SELFDESTRUCTs X again | F1: +112 charge; F2: 0; F3: 0 (selfdestructChange{prev=true} just skipped like all selfdestructChange); T.committedChildBytes=112; T (filter): X skipped once by per-address dedup → 0; delta=−112 credit | 0 | + +#### CREATE/CALL boundary scenarios + +| # | Scenario | Per-frame charges/credits | Net executionStateGas | +|---|---|---|---| +| 41 | CREATE silent failure (collision / depth / balance / nonce overflow) at depth ≥ 1 | Frame's snapshot pushed and immediately popped on failure → no journal entries → walks see nothing | 0 | +| 42 | Code-deposit OOG: initcode runs to completion, but `len(code) × cpsb + Keccak256WordGas × ⌈L/32⌉` exceeds remaining gas | `SetCode` not called → no `codeChange` in journal; `createObjectChange` is in journal but EIP-3541/etc. handling will revert via `handleFrameRevert` if applicable. If the create-frame still commits with `code = []` (Homestead pre-fork rules): walk counts +112 for the account but NOT len(code) since current code is empty | 112 (no code charge) | +| 43 | Top-level CREATE tx, contract address C: intrinsic charges 112×cpsb for C; execution then runs initcode and deploys L bytes | Top-frame walk uses `excludeCreate=C` so createObjectChange{C} skipped; codeChange{C} counts L bytes; intrinsic + execution = 112 + L | 112×cpsb (intrinsic) + L×cpsb (execution) | +| 44 | Frame mid-execution OOG (regular gas exhausted before frame commit) | Frame reverts via existing path; no commit-time walk fires → no charge | 0 (for that subtree) | +| 45 | Top-level revert (tx OOG at top frame, or top REVERT) | `evm.executionStateGas = 0` set on top-level revert path; tx-state-gas = intrinsic only | 0 (intrinsic only) | +| 46 | SystemAddress sys-call (e.g. EIP-2935 history-storage update) | `evm.chargeStateGas = false` on sys-call paths → frame-commit walk skipped entirely | 0 (sys-call must always succeed) | + +### Mixed-dimension scenarios + +| # | Scenario | Behavior | +|---|---|---| +| 47 | EIP-7702 auth list at tx start; auth target later does normal SSTORE inside main frame | Auth processing runs before top-frame `Call()` snapshot → auth's codeChange/nonceChange are at journal indices < `frameStart`; top-frame walk doesn't see them. Intrinsic_state_gas already charges 23×cpsb per auth. Main-frame SSTORE counted as usual. **No double-count.** | +| 48 | CALL to non-existent account, value=0 (post-Spurious Dragon) | EIP-161: short-circuit, account not materialized → no journal entry → 0 bytes | +| 49 | CALL to non-existent account, value>0 | Inside `evm.call()` the snapshot is pushed FIRST, then `CreateAccount(addr, false)` fires → `createObjectChange` lands in the **called** frame's segment. The called frame's walk picks up +112. Matches spec (deepest committed observer charges). | +| 50 | SELFDESTRUCT to non-existent beneficiary with value transfer | `AddBalance(beneficiary, balance)` runs inside the destroying frame's body → `createObjectChange` in that frame's segment → frame's walk attributes +112 to the destroying frame. Matches spec. | + +## Files to Modify + +### `execution/state/intra_block_state.go` +- Add `JournalLength() int` returning `len(sdb.journal.entries)`. +- Add `ComputeFrameStateBytes(start, end int, applyFilter bool, excludeCreate accounts.Address) uint64` — implements the walk above. +- **Delete** `SameTxSelfDestructedNewAccounts()` and the `SelfDestructedNewAccount` struct (lines 1441–1473) — no longer needed; the filter at top-frame walk subsumes it. + +### `execution/state/journal.go` +- **Add `originalValue uint256.Int` field to `storageChange` struct.** This is the slot's value at tx start (captured via `GetCommittedState` when the SSTORE happens). Used by `ComputeFrameStateBytes` to determine whether a write creates new state per spec. +- Same field on `fakeStorageChange` (debug path); production walk skips fakeStorageChange anyway, so not strictly required. +- Update `storageChange.revert(...)` — no behavior change; originalValue is just stored, not used in revert. +- Add a typed switch helper `(*journal) walkSegment(start, end int, fn func(idx int, e journalEntry))` if useful for keeping the walk logic in one place. Optional polish. + +### `execution/state/state_object.go` (and/or `intra_block_state.go`) +- In `stateObject.SetState(key, value)` (the journal-emitting path): when appending `storageChange{...}`, populate `originalValue` from `sdb.GetCommittedState(addr, key)`. Erigon's IBS already caches committed values via `originStorage`, so this lookup is O(1) after first hit per `(addr, key)` per tx. +- The existing journal-emit site is in `intra_block_state.go` around line 268 (`sdb.journal.append(storageChange{...})`). Add the lookup there. + +### `execution/vm/evm.go` +- Add to `EVM` struct: + - `executionStateGas uint64` (replaces `stateGasConsumed` for block accounting). + - `committedChildBytes []uint64` (per-depth stack of running children-bytes accumulators). + - `chargeStateGas bool` (false during `SysCallContract` paths to avoid charging EIP-2935 / EIP-7002 system writes). +- **Remove**: `stateGasConsumed`, `revertedSpillGas`, `stateGasRefund` fields and their accessors. Remove `CreditStateGasRefund(...)` and `RefundTxStateGas(...)` methods entirely. +- In `evm.call()` (CALL/CALLCODE/DELEGATECALL/STATICCALL) and `evm.create()`: + - At entry: capture `frameStart := ibs.JournalLength()`; push `0` onto `committedChildBytes`. + - On success commit (Amsterdam, `chargeStateGas`): compute `frameEnd`, walk via `ComputeFrameStateBytes(frameStart, frameEnd, depth==0, excludeAddr)`. Compute `delta = int64(walkTotal) - int64(committedChildBytes[depth])`. If positive, charge from `gas.State` (with spill); on insufficient set `err = ErrOutOfGas`. If negative, credit `gas.State` and decrement `executionStateGas`. Pop self; if `depth > 0`, add `walkTotal` to `committedChildBytes[depth-1]`. + - On revert/halt: pop self; do not propagate to parent. Top-level revert (`depth==0 && err != nil`) sets `evm.executionStateGas = 0`. +- Strip `handleFrameRevert`'s state-gas branches (lines 246–299). Keep only `RevertToSnapshot` and the regular-gas burn on exceptional halt. +- In `evm.create()`: + - Remove the inline `useMdGas(... stateGas := len(ret) * cpsb ...)` at line 685–687 — `codeChange` from `SetCode` is picked up by the walk. + - Simplify the `preDepositGas`/`preDepositStateGasConsumed` rollback to just `gasRemaining = preDepositGas` (regular only). + - Drop `savedStateGasConsumed`, `savedStateGasRefund`, `initialChildState` locals (and matching ones in `evm.call()`). + +### `execution/vm/operations_acl.go` +- `makeGasSStoreFunc`: + - Line 76–80 (create slot): drop the `State: 32 * cpsb` from the Amsterdam branch — return regular only. + - Line 100–105 (X→0 reset): drop `evm.CreditStateGasRefund(callContext, 32 * cpsb)`. Keep `AddRefund(...)` for regular-gas refund counter. +- `makeSelfdestructGasFn` (lines 295–301): drop the Amsterdam state-gas branch. + +### `execution/vm/gas_table.go` +- `statefulGasCall` empty-account 112×cpsb branch (around lines 514–518): drop. The `createObjectChange` from `AddBalance` to a non-existent account is picked up by the walk. + +### `execution/vm/instructions.go` +- `execCreate` (lines 1024–1086): delete the `accountStateGas` pre-deduction block (1027–1038). Delete the `CreditStateGasRefund` on failure (1076–1078). +- `opSelfdestruct6780` (lines 1341–1385): no state-gas changes needed in the opcode itself; the gas table changes above are sufficient. +- `opCall`/`opCallCode`/`opDelegateCall`/`opStaticCall`: no signature changes. The CallStipend regular-gas correction at lines 1126–1128 / 1177–1179 stays as-is. + +### `execution/protocol/txn_executor.go` +- Set `evm.chargeStateGas = true` in `Execute()` (default) and `false` in `SysCallContract` callers (search for `SysCallContract*` to enumerate sites). +- No need to communicate the top-level contract address — `evm.create()` derives it from `depth == 0` and the local `address` parameter at commit time. +- In the refund/finalize block (around lines 608–697 area): + - Replace `evm.StateGasConsumed()` with `evm.ExecutionStateGas()`. + - Drop `evm.RevertedSpillGas()` from the receipt-gas formula. + - **Delete the `SameTxSelfDestructedNewAccounts`-driven refund block entirely** — handled inside the EVM via the top-frame credit. +- `block_state_gas_used = intrinsic_state_gas + evm.ExecutionStateGas()`. +- `block_regular_gas_used = intrinsic_regular_gas + evm.regularGasConsumed`. +- `ApplyFrame` (RIP-7560 path): same treatment, ensure `chargeStateGas`, `executionStateGas`, and `committedChildBytes` are reset between AA passes. + +### `execution/tracing/hooks.go` +- Add `GasChangeFrameStateGas` to the `GasChangeReason` enum. +- Regenerate `gen_gas_change_reason_stringer.go` (`go generate ./execution/tracing/`). +- The frame-commit `useMdGas` call passes this reason. Credits emit a positive `OnGasChange(gas.State, gas.State+credit, reason)` event. + +### Removed code paths (tracking) +- `evm.CreditStateGasRefund`, `evm.RefundTxStateGas`, `evm.stateGasRefund`, `evm.revertedSpillGas`, `evm.stateGasConsumed`, `evm.StateGasConsumed`, `evm.RevertedSpillGas`. +- All Amsterdam state-gas charge paths in `gas_table.go`, `operations_acl.go`, `instructions.go`. +- The `savedStateGas*` save/restore plumbing in `evm.call()`/`evm.create()` and `handleFrameRevert`. +- `IntraBlockState.SameTxSelfDestructedNewAccounts()` and `SelfDestructedNewAccount` struct. + +## Critical files +- `execution/state/intra_block_state.go` +- `execution/state/journal.go` +- `execution/vm/evm.go` +- `execution/vm/instructions.go` +- `execution/vm/operations_acl.go` +- `execution/vm/gas_table.go` +- `execution/protocol/txn_executor.go` +- `execution/tracing/hooks.go` + +## Verification + +### Unit tests to add/update +1. `execution/protocol/misc/eip8037_test.go` — reactivate; add cases for: + - Single-frame CREATE tx: intrinsic 112×cpsb only; execution = 0. + - CREATE with code deposit: intrinsic 112×cpsb + execution `len(code)*cpsb`. + - CREATE that fails (collision, balance, OOG, REVERT): intrinsic only. + - SSTORE 0→X (same frame): execution +32×cpsb. + - SSTORE 0→X→0 same frame: execution = 0 (single-frame walk handles). + - SSTORE 0→X→0 across DELEGATECALL chain (1, 2, 3 hops): execution = 0 via top-frame negative-delta credit. + - SSTORE 0→X→0 across sibling frames: same — top-frame credit. + - CREATE-then-SELFDESTRUCT same frame: F charges 112+code+slots, T's filtered walk yields negative delta → credit; net 0. + - Cross-frame CREATE-then-SELFDESTRUCT (sibling destroys): same — net 0. + - Multiple SELFDESTRUCTs of same newly-created account: credit fires once (per-address dedup at top walk). + - Frame-end OOG: positive delta exceeds reservoir+gas_left → frame reverts; tx-state-gas = intrinsic only. + - SystemAddress sysCall (EIP-2935): no state-gas charging; sys call always succeeds regardless of journal mutations. +2. `execution/vm/gas_table_test.go` — drop assertions on state-gas charges for SSTORE/CALL/SELFDESTRUCT (now zero by design). +3. `execution/protocol/txn_executor_test.go` — keep gas-pool tests; add frame-end-OOG regression. +4. Tracer fixtures expecting per-opcode `GasChangeCallCodeStorage` for state-gas events must be updated to expect `GasChangeFrameStateGas` once per frame (potentially with a credit direction at parent commits). + +### Pre-merge checklist +- `make lint` clean. +- `make test-short` passes. +- `make test-all` passes. +- EEST EIP-8037 fixtures pass via Hive (`erigon-test-hive` skill). + +## Rollout +All changes are gated on `chainRules.IsAmsterdam` — pre-Amsterdam paths are untouched. Cutover is automatic at the fork-transition block, exercising both code paths in a single devnet run. From 3063507580dd9fbd2c79035c29add14f46d3f7ac Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:48:46 +0000 Subject: [PATCH 21/31] plan ready --- docs/plans/20260427-eip8037-journal-state-gas-spec.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index b852e8d1cda..ca583d9e81e 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -322,7 +322,6 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - `make lint` clean. - `make test-short` passes. - `make test-all` passes. -- EEST EIP-8037 fixtures pass via Hive (`erigon-test-hive` skill). ## Rollout All changes are gated on `chainRules.IsAmsterdam` — pre-Amsterdam paths are untouched. Cutover is automatic at the fork-transition block, exercising both code paths in a single devnet run. From 18dfcf1e663cbecb7cbbe54274833f778c69b0dc Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:33:47 +0000 Subject: [PATCH 22/31] initial version of changes --- execution/protocol/txn_executor.go | 86 ++--- execution/state/intra_block_state.go | 157 ++++++-- execution/state/journal.go | 14 +- execution/state/state_object.go | 18 +- execution/tests/eest_devnet/block_test.go | 1 - .../tracing/gen_gas_change_reason_stringer.go | 7 +- execution/tracing/hooks.go | 5 + execution/vm/evm.go | 351 ++++++++++-------- execution/vm/gas_table.go | 22 +- execution/vm/instructions.go | 28 +- execution/vm/interpreter.go | 7 +- execution/vm/operations_acl.go | 30 +- 12 files changed, 431 insertions(+), 295 deletions(-) diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index a8c66566fc0..7210aed8e6f 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -425,6 +425,9 @@ func (st *TxnExecutor) ApplyFrame() (*evmtypes.ExecutionResult, error) { vmerr error // vm errors do not affect consensus and are therefore not assigned to err ) + st.evm.ResetGasConsumed() + st.evm.SetChargeStateGas(true) + ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), st.data, st.gasRemaining, st.value, false) result := &evmtypes.ExecutionResult{ @@ -594,6 +597,10 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. ) st.evm.ResetGasConsumed() + // EIP-8037: enable per-frame state-gas accounting for normal tx execution. + // SysCallContract paths set this to false to exempt system-induced state + // writes (e.g. EIP-2935 history storage update). + st.evm.SetChargeStateGas(true) if contractCreation { // The reason why we don't increment nonce here is that we need the original @@ -611,62 +618,40 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. refundQuotient = params.RefundQuotientEIP3529 } if rules.IsAmsterdam { - // EIP-8037: on a successful tx, refund state gas for accounts - // that were created and self-destructed in the same tx (EIP-6780 - // means no state actually grew). Covers account creation - // (112 × cpsb), non-zero storage writes (32 × cpsb each) and - // the deployed code (len × cpsb). Clamped to the execution - // state gas used so far. - if vmerr == nil { - var stateRefund uint64 - for _, acc := range st.state.SameTxSelfDestructedNewAccounts() { - stateRefund += params.StateBytesNewAccount * st.evm.Context.CostPerStateByte - stateRefund += acc.NonZeroSlots * 32 * st.evm.Context.CostPerStateByte - stateRefund += acc.CodeLen * st.evm.Context.CostPerStateByte - } - if stateRefund > st.evm.StateGasConsumed() { - stateRefund = st.evm.StateGasConsumed() - } - if stateRefund > 0 { - st.evm.RefundTxStateGas(stateRefund) - st.gasRemaining.State += stateRefund - } - } - // EIP-8037 + EIP-7778: Block gas accounting uses two dimensions. - // stateGasConsumed tracks ALL state gas charges (including spill to regular gas). - // regularGasConsumed tracks only regular-dimension opcode gas. + // EIP-8037: per-frame state-gas accounting is journal-walk based and + // runs inside the EVM at each frame's commit. The top-frame walk + // applies the EIP-6780 selfdestruct filter, so create+destroy in + // same tx (any nesting) naturally produces a negative top-frame + // delta that credits the over-charge back. No TxnExecutor-level + // refund needed. evm.ExecutionStateGas() reflects the net charges. // - // EIP-8037: on top-level tx error, execution state gas is refunded - // to the reservoir (state_gas_used := 0). Intrinsic state gas stays - // charged. Block-level state gas therefore collapses to the - // worst-case intrinsic, and the receipt's gas-used drops the - // execution state-gas portion. - executionStateGas := st.evm.StateGasConsumed() + // Block-level state gas: zero on top-level error (state didn't grow); + // otherwise intrinsic + executionStateGas. + executionStateGas := st.evm.ExecutionStateGas() + blockExecutionStateGas := executionStateGas if vmerr != nil { - executionStateGas = 0 + blockExecutionStateGas = 0 } - blockState := imdGas.State + executionStateGas + blockState := imdGas.State + blockExecutionStateGas blockRegular := imdGas.Regular + st.evm.RegularGasConsumed() st.blockRegularGasUsed = max(blockRegular, intrinsicGasResult.FloorGasCost) st.blockStateGasUsed = blockState - // Receipt gasUsed: EIP-8037 formula tx.gas - gas_left - reservoir. - // Use Total()-level subtraction to avoid per-component uint64 underflow - // when gasRemaining.State > initialGas.State (reservoir grew via child reverts). - // RevertedSpillGas is added because handleFrameRevert restores - // the spilled state gas to gas.Regular on top-level REVERT — - // without adding it back here the receipt would under-charge by - // the spill amount. - st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + st.evm.RevertedSpillGas() + // Receipt gasUsed: tx.gas - gas_left - reservoir. Use Total()-level + // subtraction to avoid per-component uint64 underflow when + // gasRemaining.State > initialGas.State (reservoir grew via child + // reverts/credits). + st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() if vmerr != nil { - // Top-level error: subtract the execution state gas that is - // refunded to the reservoir (Python spec: - // `state_gas_left += state_gas_used; state_gas_used = 0`). - // This covers both the reservoir-drained and spill portions. - st.txnGasUsedB4Refunds -= st.evm.StateGasConsumed() + // Top-level error: per spec, execution state gas is refunded + // to the reservoir (`state_gas_left += state_gas_used; + // state_gas_used = 0`). The actual charges are still in + // gasRemaining (sub-frame charges decremented gas.State, never + // restored), so subtract them explicitly here. + st.txnGasUsedB4Refunds -= executionStateGas } - // EIP-8037: only the regular gas refund flows through the - // 20%-capped refund_counter; state gas refunds have already been - // credited to the reservoir directly via CreditStateGasRefund. + // Only the regular-gas refund flows through the 20%-capped + // refund_counter. State-gas charges/credits are already netted + // inside evm.executionStateGas via per-frame deltas. refund := min(st.txnGasUsedB4Refunds/refundQuotient, st.state.GetRefund().Regular) st.txnGasUsed = max(intrinsicGasResult.FloorGasCost, st.txnGasUsedB4Refunds-refund) } else if rules.IsPrague { @@ -682,11 +667,12 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. } st.refundGas() } else if rules.IsAmsterdam { - blockState := imdGas.State + st.evm.StateGasConsumed() + executionStateGas := st.evm.ExecutionStateGas() + blockState := imdGas.State + executionStateGas blockRegular := imdGas.Regular + st.evm.RegularGasConsumed() st.blockRegularGasUsed = max(blockRegular, intrinsicGasResult.FloorGasCost) st.blockStateGasUsed = blockState - st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + st.evm.RevertedSpillGas() + st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() st.txnGasUsed = max(st.txnGasUsedB4Refunds, intrinsicGasResult.FloorGasCost) } else { // No-refund path: gasBailout (trace_call) or !refunds. diff --git a/execution/state/intra_block_state.go b/execution/state/intra_block_state.go index ffcf7988c64..189ff6566a2 100644 --- a/execution/state/intra_block_state.go +++ b/execution/state/intra_block_state.go @@ -1438,40 +1438,6 @@ func (sdb *IntraBlockState) IsNewContract(addr accounts.Address) (bool, error) { return !delegated, nil } -// SameTxSelfDestructedNewAccounts returns addresses of contracts that were -// both created and self-destructed during the current transaction, along with -// the number of non-zero storage writes and the deployed-code length for each. -// Used by EIP-8037 to refund the account-creation / storage-set / code-deposit -// state gas charges to the tx at end-of-execution (since no state grew). -func (sdb *IntraBlockState) SameTxSelfDestructedNewAccounts() []SelfDestructedNewAccount { - var result []SelfDestructedNewAccount - for addr, so := range sdb.stateObjects { - if so == nil || !so.selfdestructed || !so.newlyCreated { - continue - } - var nonZeroSlots uint64 - for _, val := range so.dirtyStorage { - if !val.IsZero() { - nonZeroSlots++ - } - } - result = append(result, SelfDestructedNewAccount{ - Address: addr, - NonZeroSlots: nonZeroSlots, - CodeLen: uint64(len(so.code)), - }) - } - return result -} - -// SelfDestructedNewAccount summarises a contract that was created and -// self-destructed in the same transaction, for EIP-8037 refund accounting. -type SelfDestructedNewAccount struct { - Address accounts.Address - NonZeroSlots uint64 - CodeLen uint64 -} - // SetTransientState sets transient storage for a given account. It // adds the change to the journal so that it can be rolled back // to its previous value if there is a revert. @@ -1890,6 +1856,129 @@ func (sdb *IntraBlockState) PushSnapshot() int { return sdb.revisions.snapshot(sdb.journal) } +// JournalLength returns the current number of journal entries. Used by EIP-8037 +// frame-end state-gas accounting to capture frame-segment boundaries. +func (sdb *IntraBlockState) JournalLength() int { + return sdb.journal.length() +} + +// ComputeFrameStateBytes walks the journal segment journal[start:end] and +// returns the total state bytes attributable to that frame, per EIP-8037. +// +// Rules: +// - Account creations (createObjectChange / resetObjectChange), first per +// address: skip if account == excludeCreate. Skip if stateObject is nil. +// If applyFilter AND .newlyCreated && .selfdestructed, skip. Otherwise +// +StateBytesNewAccount. +// - Code deposits (codeChange), first per address: same selfdestruct filter. +// If prevhash was empty AND current stateObject.code is non-empty, +// +len(code). +// - Storage 0→non-zero (storageChange), first per (address, key): same +// filter. Use the entry's originalValue (= tx-entry value) — when +// originalValue.IsZero() AND the current value is non-zero, +32. +// +// All other entry types (selfdestruct/balance/nonce/refund/log/access-list/ +// transient/touch/balanceIncrease/fakeStorage) are skipped. +// +// applyFilter is set true only at the top frame's commit (depth==0). Sub-frames +// pass false so that EIP-6780 net-zero cases temporarily over-count and the +// negative-delta credit at top frame compensates. +// +// excludeCreate is the contract address of a top-level CREATE tx (already +// covered by the 112×CPSB intrinsic). Pass NilAddress otherwise. +func (sdb *IntraBlockState) ComputeFrameStateBytes( + start, end int, + applyFilter bool, + excludeCreate accounts.Address, +) uint64 { + if start >= end { + return 0 + } + type slotKey struct { + addr accounts.Address + key accounts.StorageKey + } + seenAccount := make(map[accounts.Address]struct{}) + seenCode := make(map[accounts.Address]struct{}) + seenSlot := make(map[slotKey]struct{}) + + // liveAccount returns the stateObject for addr and whether it should be + // counted, applying the EIP-6780 selfdestruct filter at the top frame. + liveAccount := func(addr accounts.Address) (*stateObject, bool) { + so := sdb.stateObjects[addr] + if so == nil { + return nil, false + } + if applyFilter && so.newlyCreated && so.selfdestructed { + return so, false + } + return so, true + } + + var total uint64 + for i := start; i < end && i < len(sdb.journal.entries); i++ { + switch e := sdb.journal.entries[i].(type) { + case createObjectChange: + if e.account == excludeCreate { + continue + } + if _, ok := seenAccount[e.account]; ok { + continue + } + seenAccount[e.account] = struct{}{} + if _, alive := liveAccount(e.account); alive { + total += params.StateBytesNewAccount + } + case resetObjectChange: + if e.account == excludeCreate { + continue + } + if _, ok := seenAccount[e.account]; ok { + continue + } + seenAccount[e.account] = struct{}{} + if _, alive := liveAccount(e.account); alive { + total += params.StateBytesNewAccount + } + case codeChange: + if _, ok := seenCode[e.account]; ok { + continue + } + seenCode[e.account] = struct{}{} + so, alive := liveAccount(e.account) + if !alive { + continue + } + // EIP-8037 code-deposit rule: counts only if prior code hash was + // empty and current code is non-empty. + if e.prevhash.IsEmpty() && len(so.code) > 0 { + total += uint64(len(so.code)) + } + case storageChange: + k := slotKey{addr: e.account, key: e.key} + if _, ok := seenSlot[k]; ok { + continue + } + seenSlot[k] = struct{}{} + so, alive := liveAccount(e.account) + if !alive { + continue + } + // EIP-8037 new-slot rule: tx-entry zero AND current non-zero. + if !e.originalValue.IsZero() { + continue + } + current, _ := so.GetState(e.key) + if !current.IsZero() { + total += 32 + } + default: + // All other entry types are not state-bytes-relevant. + } + } + return total +} + func (sdb *IntraBlockState) PopSnapshot(snapshot int) { sdb.revisions.returnSnapshot(snapshot) } diff --git a/execution/state/journal.go b/execution/state/journal.go index e2720d6bf44..5ca25826472 100644 --- a/execution/state/journal.go +++ b/execution/state/journal.go @@ -144,10 +144,16 @@ type ( wasCommited bool } storageChange struct { - account accounts.Address - key accounts.StorageKey - prevalue uint256.Int - wasCommited bool + account accounts.Address + key accounts.StorageKey + prevalue uint256.Int + // originalValue is the slot's value at transaction start (committed + // state). Captured at SetState time via GetCommittedState. Used by + // IntraBlockState.ComputeFrameStateBytes to determine whether a write + // creates new state per EIP-8037: a slot is "new" if originalValue == 0 + // and the current value at frame commit is non-zero. + originalValue uint256.Int + wasCommited bool } fakeStorageChange struct { account accounts.Address diff --git a/execution/state/state_object.go b/execution/state/state_object.go index 39239ef1f48..8ac10cff12b 100644 --- a/execution/state/state_object.go +++ b/execution/state/state_object.go @@ -264,12 +264,22 @@ func (so *stateObject) SetState(key accounts.StorageKey, value uint256.Int, forc return false, nil } + // EIP-8037: capture the slot's value at transaction start (committed + // state). Used by ComputeFrameStateBytes to identify new slots. + // GetCommittedState caches via originStorage so this is O(1) after the + // first call per (addr, key) per tx. + originalValue, err := so.GetCommittedState(key) + if err != nil { + return false, err + } + // New value is different, update and journal the change so.db.journal.append(storageChange{ - account: so.address, - key: key, - prevalue: prev, - wasCommited: commited, + account: so.address, + key: key, + prevalue: prev, + originalValue: originalValue, + wasCommited: commited, }) if so.db.tracingHooks != nil && so.db.tracingHooks.OnStorageChange != nil { diff --git a/execution/tests/eest_devnet/block_test.go b/execution/tests/eest_devnet/block_test.go index 5ae8ef2bfc3..c25876d8fba 100644 --- a/execution/tests/eest_devnet/block_test.go +++ b/execution/tests/eest_devnet/block_test.go @@ -62,4 +62,3 @@ func TestExecutionSpecBlockchainDevnet(t *testing.T) { } }) } - diff --git a/execution/tracing/gen_gas_change_reason_stringer.go b/execution/tracing/gen_gas_change_reason_stringer.go index 2047f5959b2..3b5dac2d54e 100644 --- a/execution/tracing/gen_gas_change_reason_stringer.go +++ b/execution/tracing/gen_gas_change_reason_stringer.go @@ -24,21 +24,22 @@ func _() { _ = x[GasChangeCallStorageColdAccess-13] _ = x[GasChangeCallFailedExecution-14] _ = x[GasChangeDelegatedDesignation-15] + _ = x[GasChangeFrameStateGas-16] _ = x[GasChangeIgnored-255] } const ( - _GasChangeReason_name_0 = "GasChangeUnspecifiedGasChangeTxInitialBalanceGasChangeTxIntrinsicGasGasChangeTxRefundsGasChangeTxLeftOverReturnedGasChangeCallInitialBalanceGasChangeCallLeftOverReturnedGasChangeCallLeftOverRefundedGasChangeCallContractCreationGasChangeCallContractCreation2GasChangeCallCodeStorageGasChangeCallOpCodeGasChangeCallPrecompiledContractGasChangeCallStorageColdAccessGasChangeCallFailedExecutionGasChangeDelegatedDesignation" + _GasChangeReason_name_0 = "GasChangeUnspecifiedGasChangeTxInitialBalanceGasChangeTxIntrinsicGasGasChangeTxRefundsGasChangeTxLeftOverReturnedGasChangeCallInitialBalanceGasChangeCallLeftOverReturnedGasChangeCallLeftOverRefundedGasChangeCallContractCreationGasChangeCallContractCreation2GasChangeCallCodeStorageGasChangeCallOpCodeGasChangeCallPrecompiledContractGasChangeCallStorageColdAccessGasChangeCallFailedExecutionGasChangeDelegatedDesignationGasChangeFrameStateGas" _GasChangeReason_name_1 = "GasChangeIgnored" ) var ( - _GasChangeReason_index_0 = [...]uint16{0, 20, 45, 68, 86, 113, 140, 169, 198, 227, 257, 281, 300, 332, 362, 390, 419} + _GasChangeReason_index_0 = [...]uint16{0, 20, 45, 68, 86, 113, 140, 169, 198, 227, 257, 281, 300, 332, 362, 390, 419, 441} ) func (i GasChangeReason) String() string { switch { - case i <= 15: + case i <= 16: return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]] case i == 255: return _GasChangeReason_name_1 diff --git a/execution/tracing/hooks.go b/execution/tracing/hooks.go index 666a0c857ee..6e338cd9af4 100644 --- a/execution/tracing/hooks.go +++ b/execution/tracing/hooks.go @@ -297,6 +297,11 @@ const ( GasChangeCallFailedExecution GasChangeReason = 14 // GasChangeDelegatedDesignation is the amount of gas that will be charged for resolution of delegated designation. GasChangeDelegatedDesignation GasChangeReason = 15 + // GasChangeFrameStateGas is the EIP-8037 per-frame state-gas charge or + // credit emitted at frame commit, computed via journal walk. + // Positive (charge) reduces gas.State (with spill into gas.Regular). + // Negative (credit) increases gas.State and decrements executionStateGas. + GasChangeFrameStateGas GasChangeReason = 16 // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // it will be "manually" tracked by a direct emit of the gas change event. diff --git a/execution/vm/evm.go b/execution/vm/evm.go index 5c6c31c4aa7..1b4c362dfea 100644 --- a/execution/vm/evm.go +++ b/execution/vm/evm.go @@ -95,16 +95,21 @@ type EVM struct { readOnly bool // Whether to throw on stateful modifications returnData []byte // Last CALL's return data for subsequent reuse - stateGasConsumed uint64 // total state gas charged during tx execution (restored on depth>0 revert, kept on depth-0) regularGasConsumed uint64 // total regular gas charged during tx execution (for block-level accounting) - revertedSpillGas uint64 // state gas that spilled to regular and was restored on depth-0 revert - // EIP-8037 state gas refund accumulated across all live frames of this tx. - // Matches python spec's `state_gas_refund`: on child-frame revert we - // subtract the child's contribution (tracked via save/restore around the - // child call) from the reservoir returned to the parent, so the inline - // refund that was credited to the child's reservoir does not leak across - // the revert boundary. - stateGasRefund uint64 + // executionStateGas accumulates the EIP-8037 state-gas charges (and credits) + // across all frame commits in this tx. Charged at frame-commit time via the + // journal-walk-and-delta algorithm (see IntraBlockState.ComputeFrameStateBytes). + // Reset to 0 on top-level revert (depth==0 && err != nil). + executionStateGas uint64 + // committedChildBytes is a per-depth stack of running totals — when a child + // frame commits, its walkTotal is added to the parent's slot. Used at + // frame-commit to compute delta = walkTotal - committedChildBytes[depth]. + committedChildBytes []uint64 + // chargeStateGas controls whether the per-frame walk-and-charge runs. + // Set to false during SysCallContract paths (EIP-2935 history storage, + // EIP-7002/EIP-7251 system calls) so those system-induced state writes do + // not consume state gas. Set true during normal tx execution by TxnExecutor. + chargeStateGas bool } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -165,70 +170,124 @@ func (evm *EVM) Cancel() { evm.abort.Store(true) } // Cancelled returns true if Cancel has been called func (evm *EVM) Cancelled() bool { return evm.abort.Load() } -// StateGasConsumed returns the total state gas charged during tx execution. -// Restored on depth>0 revert (so parent frame sees correct value), kept on depth-0 revert -// (so block gas accounting includes reverted state gas). -func (evm *EVM) StateGasConsumed() uint64 { return evm.stateGasConsumed } +// ExecutionStateGas returns the EIP-8037 execution state gas accumulated across +// all frame commits in the current tx. Reset to 0 on top-level revert. +func (evm *EVM) ExecutionStateGas() uint64 { return evm.executionStateGas } // RegularGasConsumed returns the total regular gas charged during tx execution (for block-level accounting) func (evm *EVM) RegularGasConsumed() uint64 { return evm.regularGasConsumed } -// RevertedSpillGas returns state gas that spilled to regular and was restored on depth-0 revert -func (evm *EVM) RevertedSpillGas() uint64 { return evm.revertedSpillGas } +// SetChargeStateGas configures whether per-frame state-gas accounting runs in +// this EVM instance. TxnExecutor sets true for normal tx execution and false +// for SysCallContract paths so system-induced state writes are exempt. +func (evm *EVM) SetChargeStateGas(v bool) { evm.chargeStateGas = v } // ResetGasConsumed resets the gas consumed counters for a new transaction func (evm *EVM) ResetGasConsumed() { - evm.stateGasConsumed = 0 evm.regularGasConsumed = 0 - evm.revertedSpillGas = 0 - evm.stateGasRefund = 0 + evm.executionStateGas = 0 + evm.committedChildBytes = evm.committedChildBytes[:0] } -// CreditStateGasRefund applies an EIP-8037 state gas refund: credits `amount` -// to the scope's reservoir (like python's `evm.state_gas_left += applied`), -// removes it from block-level state gas accounting, and bumps the tx-level -// refund tracker so that a subsequent frame revert can unwind the inflation. -// -// Unlike regular gas refunds (refund_counter, 20% cap at tx end), state gas -// refunds are returned immediately and uncapped, per the EIP. -func (evm *EVM) CreditStateGasRefund(ctx *CallContext, amount uint64) { - if amount == 0 { - return - } - ctx.stateGas += amount - if amount > evm.stateGasConsumed { - // Defensive clamp. In practice the charge that produced the refund - // must already be in stateGasConsumed, but CALLCODE/DELEGATECALL can - // push the matching charge up to an ancestor that shares storage. - evm.stateGasConsumed = 0 - } else { - evm.stateGasConsumed -= amount +// pushFrameAccumulator pushes a 0 onto the per-depth committedChildBytes stack +// at frame entry. Pair with popFrameAccumulator at frame exit (commit or revert). +func (evm *EVM) pushFrameAccumulator() { + evm.committedChildBytes = append(evm.committedChildBytes, 0) +} + +// popFrameAccumulator pops the top of committedChildBytes and returns its value. +// At commit time the caller passes the popped value to the parent via +// propagateChildBytes. On revert the caller discards it. +func (evm *EVM) popFrameAccumulator() uint64 { + n := len(evm.committedChildBytes) + if n == 0 { + return 0 } - evm.stateGasRefund += amount + top := evm.committedChildBytes[n-1] + evm.committedChildBytes = evm.committedChildBytes[:n-1] + return top } -// RefundTxStateGas reduces the tx-level stateGasConsumed counter. Used by -// end-of-tx refund paths (EIP-6780 same-tx selfdestruct) where there is no -// live frame scope to credit — the tx executor adds the matching amount to -// gasRemaining directly. -func (evm *EVM) RefundTxStateGas(amount uint64) { - if amount == 0 { +// propagateChildBytes adds walkTotal to the parent's committedChildBytes slot +// after a child frame commits. If we are at depth 0 (no parent) this is a no-op. +func (evm *EVM) propagateChildBytes(walkTotal uint64) { + n := len(evm.committedChildBytes) + if n == 0 { return } - if amount > evm.stateGasConsumed { - evm.stateGasConsumed = 0 + evm.committedChildBytes[n-1] += walkTotal +} + +// chargeFrameStateGas runs the EIP-8037 frame-end state-gas accounting for a +// successful call/create commit, when chargeStateGas is enabled and Amsterdam +// is active. It walks the journal segment [frameStart, frameEnd), computes +// delta = walkTotal - committedChildBytes[depth], and: +// - delta > 0 → charge delta×CPSB from gas.State (with spill into gas.Regular). +// On insufficient gas, returns ErrOutOfGas (caller must revert). +// - delta < 0 → credit |delta|×CPSB back to gas.State and decrement +// evm.executionStateGas. +// +// Returns the walkTotal so the caller can propagate it to the parent's +// accumulator. The caller is responsible for pushing/popping the per-depth +// accumulator slot via pushFrameAccumulator/popFrameAccumulator. +// +// excludeCreate is the contract address for top-level CREATE txs (depth==0 +// inside evm.create). NilAddress in all other cases. +func (evm *EVM) chargeFrameStateGas( + gas *mdgas.MdGas, + frameStart int, + depth int, + excludeCreate accounts.Address, +) (walkTotal uint64, err error) { + frameEnd := evm.intraBlockState.JournalLength() + applyFilter := depth == 0 + walkTotal = evm.intraBlockState.ComputeFrameStateBytes(frameStart, frameEnd, applyFilter, excludeCreate) + + n := len(evm.committedChildBytes) + var childTotal uint64 + if n > 0 { + childTotal = evm.committedChildBytes[n-1] + } + + if walkTotal == childTotal { + return walkTotal, nil + } + + cpsb := evm.Context.CostPerStateByte + if walkTotal > childTotal { + // Positive delta — charge it to gas.State, spilling into gas.Regular if + // the reservoir is insufficient. Tracking goes via useMdGas which + // increments evm.executionStateGas. + stateGas := (walkTotal - childTotal) * cpsb + var ok bool + *gas, ok = useMdGas(evm, *gas, stateGas, mdgas.StateGas, evm.config.Tracer, tracing.GasChangeFrameStateGas) + if !ok { + return walkTotal, ErrOutOfGas + } + return walkTotal, nil + } + // Negative delta — credit |delta|×CPSB back to gas.State and shrink the + // tx-level executionStateGas accordingly. + creditGas := (childTotal - walkTotal) * cpsb + if evm.config.Tracer != nil && evm.config.Tracer.OnGasChange != nil { + evm.config.Tracer.OnGasChange(gas.State, gas.State+creditGas, tracing.GasChangeFrameStateGas) + } + gas.State += creditGas + if creditGas > evm.executionStateGas { + evm.executionStateGas = 0 } else { - evm.stateGasConsumed -= amount + evm.executionStateGas -= creditGas } + return walkTotal, nil } -// handleFrameRevert handles the full error path for a call or create frame: -// state revert, regular gas burning on exceptional halt, and EIP-8037 state -// gas accounting (spill restoration, depth-dependent reservoir preservation). -func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, - snapshot int, - savedStateGasConsumed, savedStateGasRefund, initialChildState uint64) { - +// handleFrameRevert handles the error path for a call or create frame: +// it reverts journal state and burns remaining regular gas on exceptional halt. +// +// EIP-8037 state-gas accounting is journal-walk-based at frame commit, so a +// reverted/halted frame contributes nothing — there's nothing to roll back +// here for state gas (the frame-end charge never fired). +func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, snapshot int) { // 1. Revert state changes. evm.intraBlockState.RevertToSnapshot(snapshot, err) @@ -242,61 +301,6 @@ func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, } gas.Regular = 0 } - - // 3. EIP-8037: state gas revert accounting. - if !evm.chainRules.IsAmsterdam { - return - } - childStateConsumed := evm.stateGasConsumed - savedStateGasConsumed - // Inline state-gas refunds applied inside the child already inflated - // `gas.State` when they were credited. Unwind that inflation so the - // parent's reservoir reflects only the net-of-refund delta. - childStateGasRefund := evm.stateGasRefund - savedStateGasRefund - - // For child frames (depth > 0), restore stateGasConsumed and the refund - // tracker so the parent frame sees the pre-child value. At depth 0 there - // is no parent and we keep the full value for block gas accounting. - if depth > 0 { - evm.stateGasConsumed = savedStateGasConsumed - evm.stateGasRefund = savedStateGasRefund - } - - // EIP-8037: "On child revert or exceptional halt, all state gas - // consumed by the child, both from the reservoir and any that spilled - // into gas_left, is restored to the parent's reservoir." - if depth == 0 { - if err == ErrExecutionReverted { - // Top-level REVERT: restore spill to gas_left for refund - // accounting; track it for receipt gas calculation. - // Spill = state gas that was charged from gas_left (regular) - // because the reservoir was insufficient. When gas.State > - // initialChildState the reservoir grew via sub-child reverts — - // no reservoir was consumed net, so all childStateConsumed - // was spilled. - var reservoirUsed uint64 - if initialChildState > gas.State { - reservoirUsed = initialChildState - gas.State - } - spill := childStateConsumed - reservoirUsed - gas.Regular += spill - evm.revertedSpillGas += spill - } - // Top-level exceptional halt: gas.Regular already zeroed in step 2; - // reservoir stays as-is for block gas accounting. - } else { - // Child frame (depth > 0): restore all consumed state gas - // (reservoir-sourced + spill) to the reservoir, preserving any - // sub-child restorations already in the reservoir. Subtract the - // child's inline refunds to avoid leaking the bonus into the parent. - gas.State += childStateConsumed - if gas.State >= childStateGasRefund { - gas.State -= childStateGasRefund - } else { - gas.State = 0 - } - // Regular gas: REVERT preserves it (step 2 doesn't apply); - // exceptional halt burns it (step 2 zeroed gas.Regular). - } } // CallGasTemp returns the callGasTemp for the EVM @@ -419,9 +423,11 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts evm.intraBlockState.AddBalance(addr, u256.Num0, tracing.BalanceChangeTouchAccount) } - savedStateGasConsumed := evm.stateGasConsumed - savedStateGasRefund := evm.stateGasRefund - initialChildState := gas.State + // EIP-8037 frame-end state-gas accounting: capture the journal index at + // frame entry, push a per-depth committed-children accumulator slot. + frameStart := evm.intraBlockState.JournalLength() + evm.pushFrameAccumulator() + frameAccumulatorPopped := false // It is allowed to call precompiles, even via delegatecall if isPrecompile { @@ -440,6 +446,7 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts var codeHash accounts.CodeHash codeHash, err = evm.intraBlockState.ResolveCodeHash(addr) if err != nil { + evm.popFrameAccumulator() return nil, mdgas.MdGas{}, fmt.Errorf("%w: %w", ErrIntraBlockStateFailed, err) } var contract Contract @@ -474,11 +481,37 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts } ret, gas, err = evm.Run(contract, gas, input, readOnly) } + + // EIP-8037 frame commit (success path, Amsterdam, chargeStateGas enabled): + // run the journal-walk-and-charge before we evaluate the error path so + // that an OOG at the frame-end charge correctly triggers a revert. + if err == nil && !evm.config.RestoreState && evm.chainRules.IsAmsterdam && evm.chargeStateGas { + walkTotal, chargeErr := evm.chargeFrameStateGas(&gas, frameStart, depth, accounts.NilAddress) + if chargeErr != nil { + err = chargeErr + } else { + // Commit succeeded: pop self, propagate walkTotal to parent's accumulator. + evm.popFrameAccumulator() + frameAccumulatorPopped = true + evm.propagateChildBytes(walkTotal) + } + } + // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. if err != nil || evm.config.RestoreState { - evm.handleFrameRevert(&gas, err, depth, snapshot, savedStateGasConsumed, savedStateGasRefund, initialChildState) + if !frameAccumulatorPopped { + evm.popFrameAccumulator() + } + evm.handleFrameRevert(&gas, err, depth, snapshot) + // On top-level revert, evm.executionStateGas keeps its accumulated value + // so TxnExecutor can subtract it from the receipt gas. Block-level + // accounting uses 0 explicitly when vmerr != nil (TxnExecutor decision). + } else if !frameAccumulatorPopped { + // Non-Amsterdam or chargeStateGas disabled: pop the accumulator + // without propagating (no state-gas charging happens in this branch). + evm.popFrameAccumulator() } return ret, gas, err @@ -655,9 +688,11 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem return nil, address, gasRemaining, nil } - savedStateGasConsumed := evm.stateGasConsumed - savedStateGasRefund := evm.stateGasRefund - initialChildState := gasRemaining.State + // EIP-8037 frame-end state-gas accounting: capture the journal index at + // frame entry, push the per-depth committed-children accumulator slot. + frameStart := evm.intraBlockState.JournalLength() + evm.pushFrameAccumulator() + frameAccumulatorPopped := false ret, gasRemaining, err = evm.Run(contract, gasRemaining, nil, false) @@ -670,47 +705,38 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem err = ErrInvalidCode } // If the contract creation ran successfully and no errors were returned, - // calculate the gas required to store the code. If the code could not - // be stored due to not enough gas, set an error when we're in Homestead and let it be handled - // by the error checking condition below. + // calculate the regular gas required to store the code. If the code could + // not be stored due to not enough gas, set an error when we're in Homestead + // and let it be handled by the error checking condition below. + // + // EIP-8037: state gas for code deposit is NOT charged here — it falls out + // of the journal-walk at frame commit (via the codeChange entry SetCode + // emits below). Only the regular gas for code deposit is charged inline. if err == nil { - // EIP-8037: GAS_CODE_DEPOSIT = cpsb/byte (state) + 6*ceil(len/32) (regular) - // Pre-Amsterdam: GAS_CODE_DEPOSIT = 200/byte (regular only) preDepositGas := gasRemaining - preDepositStateGasConsumed := evm.stateGasConsumed - // Charge state gas (Amsterdam only). - stateGasOk := true + var regularGas uint64 if evm.chainRules.IsAmsterdam { - stateGas := uint64(len(ret)) * evm.Context.CostPerStateByte - gasRemaining, stateGasOk = useMdGas(evm, gasRemaining, stateGas, mdgas.StateGas, evm.Config().Tracer, tracing.GasChangeCallCodeStorage) + // EIP-8037 "Contract deployment cost calculation", success path: + // HASH_COST(L) = 6*ceil(L/32). The state component (cpsb*L) is + // derived at frame commit from the codeChange journal entry. + regularGas = params.Keccak256WordGas * ToWordSize(uint64(len(ret))) + } else { + regularGas = uint64(len(ret)) * params.CreateDataGas } - - // Charge regular gas. var regularGasOk bool - if stateGasOk { - var regularGas uint64 - if evm.chainRules.IsAmsterdam { - // EIP-8037 "Contract deployment cost calculation", success path: - // HASH_COST(L) = 6*ceil(L/32); the state component (cpsb*L) is charged above. - regularGas = params.Keccak256WordGas * ToWordSize(uint64(len(ret))) - } else { - regularGas = uint64(len(ret)) * params.CreateDataGas - } - gasRemaining, regularGasOk = useMdGas(evm, gasRemaining, regularGas, mdgas.RegularGas, evm.Config().Tracer, tracing.GasChangeCallCodeStorage) - } + gasRemaining, regularGasOk = useMdGas(evm, gasRemaining, regularGas, mdgas.RegularGas, evm.Config().Tracer, tracing.GasChangeCallCodeStorage) - if stateGasOk && regularGasOk { + if regularGasOk { evm.intraBlockState.SetCode(address, ret) } else { if evm.chainRules.IsAmsterdam { - // Code deposit failed: per EIP-8037 the failure cost is - // GAS_CREATE + initcode_execution_cost only; code deposit - // gas (both state and regular) is excluded. Undo the - // charges so that handleFrameRevert and block-level gas - // accounting see the correct values. + // Code-deposit OOG: per EIP-8037 the failure cost is + // GAS_CREATE + initcode_execution_cost only; code-deposit + // gas is excluded. Restore regular gas to pre-deposit state + // (state gas needs no rollback — SetCode hasn't fired so + // the codeChange isn't in the journal). gasRemaining = preDepositGas - evm.stateGasConsumed = preDepositStateGasConsumed } // If we run out of gas, we do not store the code: the returned code must be empty. ret = []byte{} @@ -720,11 +746,42 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem } } + // EIP-8037 frame commit (success path, Amsterdam, chargeStateGas enabled): + // run the journal-walk-and-charge before the error path so that an OOG at + // the frame-end charge correctly triggers a revert. excludeCreate is the + // contract address when this is the top-level CREATE tx (depth==0); the + // 112×CPSB intrinsic already covers it, so the walk must skip it to avoid + // double-counting. + if err == nil && evm.chainRules.IsAmsterdam && evm.chargeStateGas { + excludeCreate := accounts.NilAddress + if depth == 0 { + excludeCreate = address + } + walkTotal, chargeErr := evm.chargeFrameStateGas(&gasRemaining, frameStart, depth, excludeCreate) + if chargeErr != nil { + err = chargeErr + } else { + evm.popFrameAccumulator() + frameAccumulatorPopped = true + evm.propagateChildBytes(walkTotal) + } + } + // When an error was returned by the EVM or when setting the creation code // above, we revert to the snapshot and consume any gas remaining. Additionally, // when we're in Homestead, this also counts for code storage gas errors. if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { - evm.handleFrameRevert(&gasRemaining, err, depth, snapshot, savedStateGasConsumed, savedStateGasRefund, initialChildState) + if !frameAccumulatorPopped { + evm.popFrameAccumulator() + } + evm.handleFrameRevert(&gasRemaining, err, depth, snapshot) + // On top-level revert, evm.executionStateGas keeps its accumulated value + // so TxnExecutor can subtract it from the receipt gas. Block-level + // accounting uses 0 explicitly when vmerr != nil (TxnExecutor decision). + } else if !frameAccumulatorPopped { + // Non-Amsterdam or chargeStateGas disabled: pop the accumulator + // without propagating (no state-gas charging in this branch). + evm.popFrameAccumulator() } return ret, address, gasRemaining, err diff --git a/execution/vm/gas_table.go b/execution/vm/gas_table.go index 78defa838f7..882099dfade 100644 --- a/execution/vm/gas_table.go +++ b/execution/vm/gas_table.go @@ -498,7 +498,7 @@ func statelessGasCall(evm *EVM, callContext *CallContext, availableGas mdgas.MdG } func statefulGasCall(evm *EVM, callContext *CallContext, gas mdgas.MdGas, availableGas mdgas.MdGas, transfersValue bool) (mdgas.MdGas, error) { - var accountGas, stateGas uint64 + var accountGas uint64 var address = accounts.InternAddress(callContext.Stack.Back(1).Bytes20()) rules := evm.ChainRules() if rules.IsSpuriousDragon { @@ -510,12 +510,13 @@ func statefulGasCall(evm *EVM, callContext *CallContext, gas mdgas.MdGas, availa // tracking unconditionally, since the read happens regardless of // whether the CALL proceeds or transfers value. evm.IntraBlockState().MarkAddressAccess(address, false) - if transfersValue && empty { - if rules.IsAmsterdam { - stateGas = params.StateBytesNewAccount * evm.Context.CostPerStateByte - } else { - accountGas = params.CallNewAccountGas - } + // EIP-8037: under Amsterdam, the state gas for materializing a new + // account via CALL-with-value is no longer charged inline. The + // CreateAccount call inside evm.call() emits a createObjectChange + // after the called frame's snapshot, so the called frame's commit + // walk picks up +112. Pre-Amsterdam keeps the regular CallNewAccountGas. + if transfersValue && empty && !rules.IsAmsterdam { + accountGas = params.CallNewAccountGas } } else { exists, err := evm.IntraBlockState().Exist(address) @@ -541,13 +542,6 @@ func statefulGasCall(evm *EVM, callContext *CallContext, gas mdgas.MdGas, availa evm.intraBlockState.BlockNumber(), evm.intraBlockState.TxIndex(), evm.intraBlockState.Incarnation(), accountGas) } - if stateGas > 0 { - gas.State, overflow = math.SafeAdd(gas.State, stateGas) - if overflow { - return mdgas.MdGas{}, ErrGasUintOverflow - } - } - return gas, nil } diff --git a/execution/vm/instructions.go b/execution/vm/instructions.go index b5012bf5e3b..414576f0cfe 100644 --- a/execution/vm/instructions.go +++ b/execution/vm/instructions.go @@ -29,7 +29,6 @@ import ( "github.com/erigontech/erigon/common" "github.com/erigontech/erigon/common/hexutil" "github.com/erigontech/erigon/common/log/v3" - "github.com/erigontech/erigon/execution/protocol/mdgas" "github.com/erigontech/erigon/execution/protocol/misc" "github.com/erigontech/erigon/execution/protocol/params" "github.com/erigontech/erigon/execution/tracing" @@ -1023,20 +1022,11 @@ func opCreate2(pc uint64, evm *EVM, scope *CallContext) (uint64, []byte, error) // execCreate is the shared implementation for opCreate (salt == nil) and opCreate2 (salt != nil). func execCreate(pc uint64, evm *EVM, scope *CallContext, value uint256.Int, input []byte, salt *uint256.Int) (uint64, []byte, error) { - var accountStateGas uint64 - if evm.ChainRules().IsAmsterdam { - // EIP-8037: charge state gas for account creation after the static-context - // check so that it is not consumed on early failures where no state is - // created (per execution-specs#2608). On any failure (silent - // balance/nonce/depth/collision, child revert, child halt, code deposit - // OOG, oversized code, invalid prefix) the charge is refunded to the - // reservoir below since no account was created. - accountStateGas = uint64(params.StateBytesNewAccount) * evm.Context.CostPerStateByte - if !scope.useMdGas(evm, accountStateGas, mdgas.StateGas, evm.Config().Tracer, tracing.GasChangeIgnored) { - return pc, nil, ErrOutOfGas - } - } - + // EIP-8037: state gas for account creation is no longer pre-charged here. + // On success, the createObjectChange emitted by evm.Create's CreateAccount + // is picked up by the frame-commit walk in evm.create. On silent failure + // (collision/balance/depth/nonce overflow), the snapshot is pushed and + // immediately popped so no journal entry persists → 0 bytes counted. gas := scope.Gas() if evm.ChainRules().IsTangerineWhistle { gas.Regular -= gas.Regular / 64 @@ -1069,14 +1059,6 @@ func execCreate(pc uint64, evm *EVM, scope *CallContext, value uint256.Int, inpu scope.restoreChildGas(returnGas, evm.config.Tracer) - // EIP-8037: on any CREATE failure no account was created, so refund the - // account-creation state gas to the reservoir and back out of the block - // accounting. The 32*cpsb per-storage-slot and code-deposit charges are - // handled separately (undone by snapshot revert / preDeposit rollback). - if suberr != nil && evm.ChainRules().IsAmsterdam && accountStateGas > 0 { - evm.CreditStateGasRefund(scope, accountStateGas) - } - if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer return pc, res, nil diff --git a/execution/vm/interpreter.go b/execution/vm/interpreter.go index 0f65a9802f1..741ee8c22de 100644 --- a/execution/vm/interpreter.go +++ b/execution/vm/interpreter.go @@ -175,11 +175,14 @@ func useMdGas(evm *EVM, initial mdgas.MdGas, gas uint64, t mdgas.MdGasType, trac var ok bool switch t { case mdgas.StateGas: + // EIP-8037 frame-end charging: state gas is consumed at frame commit + // via chargeFrameStateGas, which calls useMdGas. The consumed amount + // is added to evm.executionStateGas for tx-level block accounting. originalGas := gas initial.State, ok = useGas(initial.State, gas, tracer, reason) if ok { if evm != nil { - evm.stateGasConsumed += originalGas + evm.executionStateGas += originalGas } return initial, true } @@ -188,7 +191,7 @@ func useMdGas(evm *EVM, initial mdgas.MdGas, gas uint64, t mdgas.MdGasType, trac initial.State = 0 initial.Regular, ok = useGas(initial.Regular, gas, tracer, reason) if ok && evm != nil { - evm.stateGasConsumed += originalGas + evm.executionStateGas += originalGas } return initial, ok case mdgas.RegularGas: diff --git a/execution/vm/operations_acl.go b/execution/vm/operations_acl.go index 4aa0a854124..bf661a4a237 100644 --- a/execution/vm/operations_acl.go +++ b/execution/vm/operations_acl.go @@ -74,10 +74,13 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { if original.Eq(¤t) { if original.IsZero() { // create slot (2.1.1) if rules.IsAmsterdam { - return mdgas.MdGas{Regular: cost + params.SstoreSetGasEIP8037, State: 32 * evm.Context.CostPerStateByte}, nil - } else { - return mdgas.MdGas{Regular: cost + params.SstoreSetGasEIP2200}, nil + // EIP-8037: state gas for the new slot is no longer charged + // inline. It is computed at frame commit by the journal walk + // (see IntraBlockState.ComputeFrameStateBytes). Only the + // regular-gas component changes here. + return mdgas.MdGas{Regular: cost + params.SstoreSetGasEIP8037}, nil } + return mdgas.MdGas{Regular: cost + params.SstoreSetGasEIP2200}, nil } if value.IsZero() { // delete slot (2.1.2b) evm.IntraBlockState().AddRefund(clearingRefund) @@ -98,11 +101,12 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // EIP 2200 Original clause: //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) if rules.IsAmsterdam { - // EIP-8037: regular gas refund still flows through the 20%-capped - // refund_counter; state gas refund is uncapped and credited - // directly to the current frame's reservoir. + // EIP-8037: regular-gas refund still flows through the 20%-capped + // refund_counter. The state-gas component is no longer credited + // inline — the slot's net contribution is computed at frame + // commit via the journal walk, so a 0→X→0 within the same tx + // naturally produces 0 state gas without any explicit refund. evm.IntraBlockState().AddRefund(params.SstoreSetGasEIP8037 - params.WarmStorageReadCostEIP2929) - evm.CreditStateGasRefund(callContext, 32*evm.Context.CostPerStateByte) } else { evm.IntraBlockState().AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929) } @@ -292,12 +296,12 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if balance.IsZero() && address != callContext.Address() { evm.IntraBlockState().MarkNewReadsInternal(address, beneficiaryReadsBefore) } - if empty && !balance.IsZero() { - if evm.chainRules.IsAmsterdam { - gas.State = params.StateBytesNewAccount * evm.Context.CostPerStateByte - } else { - gas.Regular += params.CreateBySelfdestructGas - } + // EIP-8037: under Amsterdam, the state gas for a new beneficiary account + // is no longer charged inline — the AddBalance call inside opSelfdestruct + // emits a createObjectChange that's picked up by the frame-commit walk. + // Pre-Amsterdam still charges CreateBySelfdestructGas (regular only). + if empty && !balance.IsZero() && !evm.chainRules.IsAmsterdam { + gas.Regular += params.CreateBySelfdestructGas } hasSelfdestructed, err := evm.IntraBlockState().HasSelfdestructed(callContext.Address()) From 60247744951efa521e38ed102f9a9453a46ad93d Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:39:06 +0000 Subject: [PATCH 23/31] add found test challenges --- ...20260427-eip8037-journal-state-gas-spec.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index ca583d9e81e..02523a21cd4 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -121,6 +121,70 @@ For **accounts and code**, no equivalent tx-entry field is needed because the EV So the walk's "first createObjectChange/codeChange per address" rule is spec-correct without any tx-entry comparison. No remaining divergence. +## Found test challenges + +After implementing the plan and running `TestExecutionSpecBlockchainDevnet` from `execution/tests/eest_devnet/`, **1,857 of 15,429 subtests fail** (~12%). The implementation is consistent with EIP-8037 PR 11573, but the EEST fixtures pre-date PR 11573 and were generated against the old opcode-time state-gas-charging semantics. + +The fixture submodule at `execution/tests/execution-spec-tests` is pinned to commit `6c2af7fc95d1c0aa781898b1a7ad78769a536d7f` ("add snolbal-devnet-4 fixtures with static cpsb"), which is from **before** PR 11573's "fixed CPSB + frame accounting" rewrite. As a result the fixtures expect: + +- State-gas charges to fire at the SSTORE/CREATE/CALL-to-empty opcodes inline, spilling into `gas_left` when `state_gas_reservoir == 0`. +- Receipt gas to subtract the `stateGasConsumed` on top-level revert/halt (effectively refunding the spilled regular gas). + +Our implementation (per PR 11573): +- Charges state gas only at frame commit via journal walk. +- On top-level revert/halt, the commit-time charge never fires → state gas = 0; remaining regular gas is fully burned. + +The math on a representative failure confirms the divergence comes from this single source. + +### Pattern verification: `dupn_stack_underflow.json` + +Test path: `for_amsterdam/amsterdam/eip8024_dupn_swapn_exchange/dupn/dupn_stack_underflow.json` → `test_dupn_stack_underflow[fork_Amsterdam-blockchain_test_from_state_test-dupn_underflow_imm_0]`. + +Tx gas limit: 1,000,000. Bytecode: `PUSH1 1 PUSH1 0 SSTORE` then DUPN underflow (exceptional halt at top frame). + +| Quantity | Old EIP-8037 (fixture) | PR 11573 (our impl) | +|---|---|---| +| SSTORE 0→1 regular gas | 5,000 | 5,000 | +| SSTORE 0→1 state gas | 32 × 1,174 = 37,568 (charged inline, spills to `gas_left`) | 0 (deferred to frame commit) | +| Frame commit fires? | n/a (charged inline) | No — exceptional halt aborts | +| Remaining gas at halt | 979,000 − 5,000 − 37,568 = 936,432 | 979,000 − 5,000 = 974,000 | +| Burned on halt | 936,432 | 974,000 | +| `regularGasConsumed` | 5,000 + 936,432 = 941,432 | 5,000 + 974,000 = 979,000 | +| Receipt subtracts `stateGasConsumed`? | 37,568 (yes) | 0 (no) | +| Block `gas_used` (header) | 21,000 + 941,432 = **962,432** | 21,000 + 979,000 = **1,000,000** | + +Difference: exactly 37,568 = 32 × CPSB (one slot-set worth of state gas). + +### Failing test categories + +All five failing categories live under `for_amsterdam/amsterdam/`: + +| Category | Representative failing tests | +|---|---| +| `eip8024_dupn_swapn_exchange` (DUPN/SWAPN/EXCHANGE) | `dupn/dupn_stack_underflow.json::test_dupn_stack_underflow[*]` (all 6 imm variants) | +| `eip7954_increase_max_contract_size` (max code size) | `max_code_size/max_code_size_deposit_gas.json::test_max_code_size_deposit_gas[short_one_gas]` | +| `eip7928_block_level_access_lists` (BAL — block-level access lists) | `block_access_lists_opcodes/bal_create_oog_code_deposit.json`; `bal_create_contract_init_revert.json`; `bal_create2_collision.json`; `bal_create_and_oog.json[CREATE/CREATE2 × oog_before/after_target_access]`; `block_access_lists/bal_net_zero_balance_transfer.json[zero_balance_zero_transfer_selfdestruct]`; `bal_nonexistent_account_access_read_only.json[staticcall]`; `bal_aborted_storage_access.json[invalid]`; `bal_precompile_call.json[0x01..0x100]` | +| `eip7708_eth_transfer_logs` (ETH transfer logs) | `transfer_logs/zero_value_operations_no_log.json[selfdestruct]`; `transfer_logs/selfdestruct_to_system_address.json`; `transfer_logs/failed_create_with_value_no_log.json[initcode_invalid]`; `transfer_logs/create_collision_no_log.json[CREATE/CREATE2]`; `transfer_logs/create_out_of_gas_no_log.json[create_out_of_gas_code_deposit]` | +| `eip8037_state_creation_gas_cost_increase` (the EIP we refactored) | broad coverage; many subtests across the EIP-8037 fixture set | + +Common failure mode across all categories: the fixture's expected `gasUsed` reflects the OLD EIP-8037 state-gas spillover behaviour; under our PR-11573-aligned model, `gasUsed` is higher by some multiple of 32 × CPSB or 112 × CPSB depending on what state-gas charges the test exercises. + +### Other test signals + +- `make lint` — clean. +- `make test-short` — passes for all packages. +- `make erigon` and `make integration` — both build clean. +- Pre-Amsterdam fork tests in `TestExecutionSpecBlockchain` (and the per-fork `*Cancun*` / `*Prague*` / `*Osaka*` variants) — pass; no divergence outside Amsterdam. + +### Recommended next step + +Per the `erigon-implement-eip` skill ("question the tests — do not silently fix them"): the EEST fixtures need to be regenerated against the PR-11573-aligned python-spec implementation. Until that lands upstream, the 1,857 Amsterdam-EIP fixture failures are expected and should be documented in the PR description rather than worked around in the implementation. + +If a sooner check is needed, options are: +1. Wait for upstream EEST regeneration against PR 11573 and re-pin the submodule. +2. Hand-craft per-test expected values in a local override layer (fragile and high-maintenance). +3. Hold the implementation behind a temporary fork-rules feature flag while both spec lines stabilise (defers the divergence rather than resolving it). + ## Edge cases (worked traces) The "originalValue (tx-entry) + current-value-at-commit" rule, combined with the per-frame `delta = walk − committedChildBytes` charge/credit, handles these without any opcode-level refund plumbing. All scenarios assume EIP-8037 (`IsAmsterdam`) and `cpsb = cost_per_state_byte`. Since `storageChange.originalValue` is the slot's value at tx start (captured via `GetCommittedState`), pre-S=X scenarios short-circuit to 0 bytes immediately — the slot was already non-zero at tx start, so no rule can identify it as "new state". From 02753ba98b289a84317b75cba7dcb167929a65e8 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:03:49 +0000 Subject: [PATCH 24/31] update plan --- ...20260427-eip8037-journal-state-gas-spec.md | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index 02523a21cd4..f01fcbe47c5 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -20,6 +20,7 @@ This sprawl has produced bugs the snøbal-devnet-4 test suite has surfaced, and 4. **SELFDESTRUCT filter applied only at the top-frame walk**: sub-frames walk *without* the `.newlyCreated && .selfdestructed` filter (so EIP-6780 net-0 cases temporarily over-count and OOG mid-execution exactly as the spec wants — state IS allocated transiently). At the top frame's commit (depth==0), the walk DOES apply the filter, which makes the top-frame walk `< committed_children_total`, producing the negative delta that credits the over-charge back. Multiple selfdestructs of the same account are deduplicated naturally because the walk keys on address (first occurrence per address). 5. **Top-level CREATE tx**: pass `excludeAddr` so the contract address (already covered by the 112×cpsb intrinsic) is not double-counted by the top-frame walk. Snapshot stays where it is; the top-level `createObjectChange` is just skipped in the walk. The exclusion is derived in-place from `depth == 0` inside `evm.create()` (where the contract address is already a local parameter) — no coordination from TxnExecutor needed, no field on the EVM struct. 6. **Per-depth `committedChildBytes` accumulator on the EVM**, populated by each child as it commits. No journal-range bookkeeping (`frameChildRanges` is unnecessary because the running total of children's contributions, combined with the parent's full-segment walk and credit-on-negative-delta, is strictly more correct than range exclusion — the latter would miss cross-frame SSTORE 0→X→0). +7. **System calls (EIP-2935 / EIP-4788 / EIP-7002 / EIP-7251 etc.) charge state gas internally via the journal walk, but with a dedicated pre-sized reservoir** (per [EIPs PR 11573 commit `d2a0230`](https://github.com/ethereum/EIPs/pull/11573/changes/d2a023056187fb17c94e9477cadd076a0f817760)). The system-call MdGas is initialised as `{Regular: 30_000_000, State: STATE_BYTES_PER_STORAGE_SET × CPSB × SYSTEM_MAX_SSTORES_PER_CALL}` where `SYSTEM_MAX_SSTORES_PER_CALL = 16`. Total `SYSTEM_CALL_GAS_LIMIT = 30_000_000 + 32 × 1_174 × 16 = 30_601_088`. System calls remain not subject to `TX_MAX_GAS_LIMIT`, do not count against the block gas limit, and do not contribute to either `block_regular_gas_used` or `block_state_gas_used` — but they DO walk the journal at frame commit and DO charge state gas from their dedicated reservoir (so each system call can SSTORE up to 16 fresh slots without OOG'ing on state gas). ## Algorithm @@ -32,7 +33,7 @@ Frame entry (evm.call / evm.create): Frame body runs (regular gas charged inline as today; no state-gas charges). -Frame commit (success path, IsAmsterdam, chargeStateGas): +Frame commit (success path, IsAmsterdam, !RestoreState): frameEnd = ibs.JournalLength() applyFilt = (depth == 0) // filter only at top frame excludeC = address if (in evm.create() && depth == 0) else NilAddress @@ -272,7 +273,7 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- | 43 | Top-level CREATE tx, contract address C: intrinsic charges 112×cpsb for C; execution then runs initcode and deploys L bytes | Top-frame walk uses `excludeCreate=C` so createObjectChange{C} skipped; codeChange{C} counts L bytes; intrinsic + execution = 112 + L | 112×cpsb (intrinsic) + L×cpsb (execution) | | 44 | Frame mid-execution OOG (regular gas exhausted before frame commit) | Frame reverts via existing path; no commit-time walk fires → no charge | 0 (for that subtree) | | 45 | Top-level revert (tx OOG at top frame, or top REVERT) | `evm.executionStateGas = 0` set on top-level revert path; tx-state-gas = intrinsic only | 0 (intrinsic only) | -| 46 | SystemAddress sys-call (e.g. EIP-2935 history-storage update) | `evm.chargeStateGas = false` on sys-call paths → frame-commit walk skipped entirely | 0 (sys-call must always succeed) | +| 46 | SystemAddress sys-call (e.g. EIP-2935 history-storage update) | SysCallContract initialises `gasRemaining = {Regular: 30_000_000, State: 32 × CPSB × 16 = 601_088}` per the SYSTEM_CALL_GAS_LIMIT formula. The frame-commit walk fires (gated on `IsAmsterdam && !RestoreState` only — no special flag) and the per-frame state-gas charge draws from the dedicated reservoir (covers up to 16 fresh storage writes; e.g. 16-slot history buffer for EIP-2935). System calls do not contribute to `block_regular_gas_used` or `block_state_gas_used` — TxnExecutor is not involved, so the EVM's accumulated `executionStateGas`/`regularGasConsumed` are simply discarded after the call returns. | not contributing to block gas; sys call's own reservoir covers up to `SYSTEM_MAX_SSTORES_PER_CALL` slot writes | ### Mixed-dimension scenarios @@ -300,15 +301,19 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - In `stateObject.SetState(key, value)` (the journal-emitting path): when appending `storageChange{...}`, populate `originalValue` from `sdb.GetCommittedState(addr, key)`. Erigon's IBS already caches committed values via `originStorage`, so this lookup is O(1) after first hit per `(addr, key)` per tx. - The existing journal-emit site is in `intra_block_state.go` around line 268 (`sdb.journal.append(storageChange{...})`). Add the lookup there. +### `execution/protocol/params/protocol.go` +- Add `SystemMaxSstoresPerCall = 16` (per [PR 11573 commit `d2a0230`](https://github.com/ethereum/EIPs/pull/11573/changes/d2a023056187fb17c94e9477cadd076a0f817760)) — upper bound on fresh storage slots a single system call writes. +- Add a helper `SystemCallGasLimit(cpsb uint64) uint64` returning `30_000_000 + StateBytesPerStorageSlot × cpsb × SystemMaxSstoresPerCall` (or use the existing `SysCallGasLimit = 30_000_000` and a separate `SystemCallStateReservoir(cpsb uint64) uint64 = 32 × cpsb × SystemMaxSstoresPerCall` to keep the regular and state portions clearly separated). + ### `execution/vm/evm.go` - Add to `EVM` struct: - `executionStateGas uint64` (replaces `stateGasConsumed` for block accounting). - `committedChildBytes []uint64` (per-depth stack of running children-bytes accumulators). - - `chargeStateGas bool` (false during `SysCallContract` paths to avoid charging EIP-2935 / EIP-7002 system writes). +- **No `chargeStateGas` flag.** Under PR 11573 commit `d2a0230`, system calls also charge state gas via the journal walk (with a pre-sized reservoir, see `SysCallContract` below). The walk's gate is just `IsAmsterdam && !RestoreState`. Block-accounting exemption for system calls is automatic because they don't go through `TxnExecutor`. - **Remove**: `stateGasConsumed`, `revertedSpillGas`, `stateGasRefund` fields and their accessors. Remove `CreditStateGasRefund(...)` and `RefundTxStateGas(...)` methods entirely. - In `evm.call()` (CALL/CALLCODE/DELEGATECALL/STATICCALL) and `evm.create()`: - At entry: capture `frameStart := ibs.JournalLength()`; push `0` onto `committedChildBytes`. - - On success commit (Amsterdam, `chargeStateGas`): compute `frameEnd`, walk via `ComputeFrameStateBytes(frameStart, frameEnd, depth==0, excludeAddr)`. Compute `delta = int64(walkTotal) - int64(committedChildBytes[depth])`. If positive, charge from `gas.State` (with spill); on insufficient set `err = ErrOutOfGas`. If negative, credit `gas.State` and decrement `executionStateGas`. Pop self; if `depth > 0`, add `walkTotal` to `committedChildBytes[depth-1]`. + - On success commit (`IsAmsterdam && !RestoreState`): compute `frameEnd`, walk via `ComputeFrameStateBytes(frameStart, frameEnd, depth==0, excludeAddr)`. Compute `delta = int64(walkTotal) - int64(committedChildBytes[depth])`. If positive, charge from `gas.State` (with spill); on insufficient set `err = ErrOutOfGas`. If negative, credit `gas.State` and decrement `executionStateGas`. Pop self; if `depth > 0`, add `walkTotal` to `committedChildBytes[depth-1]`. - On revert/halt: pop self; do not propagate to parent. Top-level revert (`depth==0 && err != nil`) sets `evm.executionStateGas = 0`. - Strip `handleFrameRevert`'s state-gas branches (lines 246–299). Keep only `RevertToSnapshot` and the regular-gas burn on exceptional halt. - In `evm.create()`: @@ -331,15 +336,29 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - `opCall`/`opCallCode`/`opDelegateCall`/`opStaticCall`: no signature changes. The CallStipend regular-gas correction at lines 1126–1128 / 1177–1179 stays as-is. ### `execution/protocol/txn_executor.go` -- Set `evm.chargeStateGas = true` in `Execute()` (default) and `false` in `SysCallContract` callers (search for `SysCallContract*` to enumerate sites). - No need to communicate the top-level contract address — `evm.create()` derives it from `depth == 0` and the local `address` parameter at commit time. +- No need to set a `chargeStateGas` flag — the walk is gated on `IsAmsterdam && !RestoreState` only. - In the refund/finalize block (around lines 608–697 area): - Replace `evm.StateGasConsumed()` with `evm.ExecutionStateGas()`. - Drop `evm.RevertedSpillGas()` from the receipt-gas formula. - **Delete the `SameTxSelfDestructedNewAccounts`-driven refund block entirely** — handled inside the EVM via the top-frame credit. - `block_state_gas_used = intrinsic_state_gas + evm.ExecutionStateGas()`. - `block_regular_gas_used = intrinsic_regular_gas + evm.regularGasConsumed`. -- `ApplyFrame` (RIP-7560 path): same treatment, ensure `chargeStateGas`, `executionStateGas`, and `committedChildBytes` are reset between AA passes. +- `ApplyFrame` (RIP-7560 path): same treatment, ensure `executionStateGas` and `committedChildBytes` are reset between AA passes. + +### `execution/protocol/block_exec.go` (`SysCallContract` / `SysCallContractWithBlockContext`) +- No flag to set — the walk fires automatically on Amsterdam (gated only on `IsAmsterdam && !RestoreState`). What changes is the gas split: +- Initialise `mdGas` with the new SYSTEM_CALL_GAS_LIMIT split: + ```go + cpsb := blockContext.CostPerStateByte + mdGas := mdgas.MdGas{ + Regular: params.SysCallGasLimit, // 30_000_000 + State: 32 * cpsb * params.SystemMaxSstoresPerCall, // dedicated reservoir + } + ``` + This matches `SYSTEM_CALL_GAS_LIMIT = 30_000_000 + STATE_BYTES_PER_STORAGE_SET × CPSB × SYSTEM_MAX_SSTORES_PER_CALL` from PR 11573 commit `d2a0230`. With CPSB=1174 the reservoir is `32 × 1174 × 16 = 601_088` state gas, enough to cover up to 16 fresh storage writes per call (sufficient for EIP-2935 history-buffer updates, EIP-4788 beacon-root, EIP-7002 / EIP-7251 system-contract operations). +- For pre-Amsterdam forks the `State: 0` initialisation stays (no state-gas dimension before EIP-8037). Gate the reservoir initialisation on `chainConfig.Rules(blockContext...)` having `IsAmsterdam`. +- System calls remain not subject to `TX_MAX_GAS_LIMIT`, do not count against the block gas limit, and do not contribute to either `block_regular_gas_used` or `block_state_gas_used` — automatic in our model because `SysCallContract` does not invoke `TxnExecutor`, so there is no add-step for the EVM's `executionStateGas`/`regularGasConsumed`. After the call returns, the EVM is discarded and its counters are dropped. ### `execution/tracing/hooks.go` - Add `GasChangeFrameStateGas` to the `GasChangeReason` enum. @@ -351,6 +370,7 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - All Amsterdam state-gas charge paths in `gas_table.go`, `operations_acl.go`, `instructions.go`. - The `savedStateGas*` save/restore plumbing in `evm.call()`/`evm.create()` and `handleFrameRevert`. - `IntraBlockState.SameTxSelfDestructedNewAccounts()` and `SelfDestructedNewAccount` struct. +- `evm.chargeStateGas` field and `evm.SetChargeStateGas` setter — the walk is gated solely on `IsAmsterdam && !RestoreState`. ## Critical files - `execution/state/intra_block_state.go` @@ -377,7 +397,7 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - Cross-frame CREATE-then-SELFDESTRUCT (sibling destroys): same — net 0. - Multiple SELFDESTRUCTs of same newly-created account: credit fires once (per-address dedup at top walk). - Frame-end OOG: positive delta exceeds reservoir+gas_left → frame reverts; tx-state-gas = intrinsic only. - - SystemAddress sysCall (EIP-2935): no state-gas charging; sys call always succeeds regardless of journal mutations. + - SystemAddress sysCall (EIP-2935 history-buffer ring update writes 1 slot per block; EIP-4788 beacon-root similar): SysCallContract initialises reservoir = `32 × cpsb × 16 = 601_088`, walk fires (gated only on `IsAmsterdam && !RestoreState`) and charges state gas for the slot writes (well within the dedicated reservoir budget). Verify the call does not contribute to `block_*_gas_used`. 2. `execution/vm/gas_table_test.go` — drop assertions on state-gas charges for SSTORE/CALL/SELFDESTRUCT (now zero by design). 3. `execution/protocol/txn_executor_test.go` — keep gas-pool tests; add frame-end-OOG regression. 4. Tracer fixtures expecting per-opcode `GasChangeCallCodeStorage` for state-gas events must be updated to expect `GasChangeFrameStateGas` once per frame (potentially with a credit direction at parent commits). From eba89886a084006c36c54825d0c7001b950a6af3 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:29:34 +0000 Subject: [PATCH 25/31] update system txns logic --- execution/protocol/block_exec.go | 11 +++++++++- execution/protocol/params/protocol.go | 9 ++++++++ execution/protocol/txn_executor.go | 5 ----- execution/vm/evm.go | 30 +++++++++------------------ 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/execution/protocol/block_exec.go b/execution/protocol/block_exec.go index e6b25e54eda..6f7175c5c9a 100644 --- a/execution/protocol/block_exec.go +++ b/execution/protocol/block_exec.go @@ -270,9 +270,18 @@ func SysCallContractWithBlockContext(contract accounts.Address, data []byte, cha txContext = NewEVMTxContext(msg) } evm := vm.NewEVM(blockContext, txContext, ibs, chainConfig, vmConfig) + // EIP-8037 (PR 11573 commit d2a0230): system calls are not subject to the + // TX_MAX_GAS_LIMIT cap, do not count against the block gas limit, and do + // not contribute to either block_regular_gas_used or block_state_gas_used. + // On Amsterdam, they get a dedicated state-gas reservoir sized for up to + // SystemMaxSstoresPerCall (=16) fresh storage slots; the regular budget + // (msg.Gas() = SysCallGasLimit = 30M) sits alongside it as gas_left. mdGas := mdgas.MdGas{ Regular: msg.Gas(), - State: 0, // state gas reservoir will consume from regular gas for sys calls + State: 0, + } + if evm.ChainRules().IsAmsterdam { + mdGas.State = params.StateBytesPerStorageSlot * evm.Context.CostPerStateByte * params.SystemMaxSstoresPerCall } ret, _, err := evm.Call( msg.From(), diff --git a/execution/protocol/params/protocol.go b/execution/protocol/params/protocol.go index d5bf400598f..e8f0155c469 100644 --- a/execution/protocol/params/protocol.go +++ b/execution/protocol/params/protocol.go @@ -223,6 +223,15 @@ const ( PerAuthBaseCostEIP8037 = 7_500 StateBytesNewAccount = 112 // bytes per new account creation StateBytesAuthBase = 23 // bytes per authorization base cost + StateBytesPerStorageSlot = 32 // bytes per fresh non-zero storage slot + // SystemMaxSstoresPerCall is the upper bound on the number of new storage + // slots a single system call (EIP-2935 / EIP-4788 / EIP-7002 / EIP-7251 etc.) + // is expected to write. Per EIPs PR 11573 commit d2a0230, the system call's + // state_gas_reservoir is sized as + // StateBytesPerStorageSlot × CostPerStateByte × SystemMaxSstoresPerCall, so + // a single system call can SSTORE up to this many fresh slots without + // running out of state gas. + SystemMaxSstoresPerCall uint64 = 16 ) // EIP-7702: Set EOA account code diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index 7210aed8e6f..0df00eeec2a 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -426,7 +426,6 @@ func (st *TxnExecutor) ApplyFrame() (*evmtypes.ExecutionResult, error) { ) st.evm.ResetGasConsumed() - st.evm.SetChargeStateGas(true) ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), st.data, st.gasRemaining, st.value, false) @@ -597,10 +596,6 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. ) st.evm.ResetGasConsumed() - // EIP-8037: enable per-frame state-gas accounting for normal tx execution. - // SysCallContract paths set this to false to exempt system-induced state - // writes (e.g. EIP-2935 history storage update). - st.evm.SetChargeStateGas(true) if contractCreation { // The reason why we don't increment nonce here is that we need the original diff --git a/execution/vm/evm.go b/execution/vm/evm.go index 1b4c362dfea..3efcc5413b9 100644 --- a/execution/vm/evm.go +++ b/execution/vm/evm.go @@ -105,11 +105,6 @@ type EVM struct { // frame commits, its walkTotal is added to the parent's slot. Used at // frame-commit to compute delta = walkTotal - committedChildBytes[depth]. committedChildBytes []uint64 - // chargeStateGas controls whether the per-frame walk-and-charge runs. - // Set to false during SysCallContract paths (EIP-2935 history storage, - // EIP-7002/EIP-7251 system calls) so those system-induced state writes do - // not consume state gas. Set true during normal tx execution by TxnExecutor. - chargeStateGas bool } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -177,11 +172,6 @@ func (evm *EVM) ExecutionStateGas() uint64 { return evm.executionStateGas } // RegularGasConsumed returns the total regular gas charged during tx execution (for block-level accounting) func (evm *EVM) RegularGasConsumed() uint64 { return evm.regularGasConsumed } -// SetChargeStateGas configures whether per-frame state-gas accounting runs in -// this EVM instance. TxnExecutor sets true for normal tx execution and false -// for SysCallContract paths so system-induced state writes are exempt. -func (evm *EVM) SetChargeStateGas(v bool) { evm.chargeStateGas = v } - // ResetGasConsumed resets the gas consumed counters for a new transaction func (evm *EVM) ResetGasConsumed() { evm.regularGasConsumed = 0 @@ -219,8 +209,8 @@ func (evm *EVM) propagateChildBytes(walkTotal uint64) { } // chargeFrameStateGas runs the EIP-8037 frame-end state-gas accounting for a -// successful call/create commit, when chargeStateGas is enabled and Amsterdam -// is active. It walks the journal segment [frameStart, frameEnd), computes +// successful call/create commit, when Amsterdam is active and the EVM is not +// in RestoreState mode. It walks the journal segment [frameStart, frameEnd), computes // delta = walkTotal - committedChildBytes[depth], and: // - delta > 0 → charge delta×CPSB from gas.State (with spill into gas.Regular). // On insufficient gas, returns ErrOutOfGas (caller must revert). @@ -482,10 +472,10 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts ret, gas, err = evm.Run(contract, gas, input, readOnly) } - // EIP-8037 frame commit (success path, Amsterdam, chargeStateGas enabled): + // EIP-8037 frame commit (success path, Amsterdam, not in RestoreState): // run the journal-walk-and-charge before we evaluate the error path so // that an OOG at the frame-end charge correctly triggers a revert. - if err == nil && !evm.config.RestoreState && evm.chainRules.IsAmsterdam && evm.chargeStateGas { + if err == nil && !evm.config.RestoreState && evm.chainRules.IsAmsterdam { walkTotal, chargeErr := evm.chargeFrameStateGas(&gas, frameStart, depth, accounts.NilAddress) if chargeErr != nil { err = chargeErr @@ -509,8 +499,8 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts // so TxnExecutor can subtract it from the receipt gas. Block-level // accounting uses 0 explicitly when vmerr != nil (TxnExecutor decision). } else if !frameAccumulatorPopped { - // Non-Amsterdam or chargeStateGas disabled: pop the accumulator - // without propagating (no state-gas charging happens in this branch). + // Pre-Amsterdam: no state-gas charging happens in this branch; pop the + // accumulator without propagating. evm.popFrameAccumulator() } @@ -746,13 +736,13 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem } } - // EIP-8037 frame commit (success path, Amsterdam, chargeStateGas enabled): + // EIP-8037 frame commit (success path, Amsterdam, not in RestoreState): // run the journal-walk-and-charge before the error path so that an OOG at // the frame-end charge correctly triggers a revert. excludeCreate is the // contract address when this is the top-level CREATE tx (depth==0); the // 112×CPSB intrinsic already covers it, so the walk must skip it to avoid // double-counting. - if err == nil && evm.chainRules.IsAmsterdam && evm.chargeStateGas { + if err == nil && !evm.config.RestoreState && evm.chainRules.IsAmsterdam { excludeCreate := accounts.NilAddress if depth == 0 { excludeCreate = address @@ -779,8 +769,8 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem // so TxnExecutor can subtract it from the receipt gas. Block-level // accounting uses 0 explicitly when vmerr != nil (TxnExecutor decision). } else if !frameAccumulatorPopped { - // Non-Amsterdam or chargeStateGas disabled: pop the accumulator - // without propagating (no state-gas charging in this branch). + // Pre-Amsterdam or RestoreState: pop the accumulator without + // propagating (no state-gas charging happens in this branch). evm.popFrameAccumulator() } From 3f95dbafd0d412129fb19ec00474188a1c027a50 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:58:54 +0000 Subject: [PATCH 26/31] refill state gas for halt/revert --- ...20260427-eip8037-journal-state-gas-spec.md | 25 +++++++++--- execution/vm/evm.go | 39 +++++++++++++++---- execution/vm/interpreter.go | 17 +++++--- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index f01fcbe47c5..f023b337574 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -59,11 +59,21 @@ Frame commit (success path, IsAmsterdam, !RestoreState): evm.committedChildBytes[depth-1] += walkTotal // propagate to parent Frame revert/halt: - RevertToSnapshot // journal undoes everything + poppedChildBytes = pop evm.committedChildBytes[depth] // do NOT add to parent + if IsAmsterdam && !RestoreState && poppedChildBytes > 0: + // EIP-8037: restore all state gas consumed by committed descendants + // to this frame's reservoir (which is then propagated to the parent + // via restoreChildGas). Spec: "On child revert or exceptional halt, + // all state gas consumed by the child, both from the reservoir and + // any that spilled into gas_left, is restored to the parent's + // reservoir." At depth==0 this naturally zeroes evm.executionStateGas + // because the top frame's accumulator holds the sum of all charged + // bytes for the tx. + restoreGas = poppedChildBytes * cpsb + gas.State += restoreGas + evm.executionStateGas -= restoreGas // saturate at 0 + RevertToSnapshot // journal undoes everything burn regular gas on exceptional halt (same as today) - pop evm.committedChildBytes[depth] // do NOT add to parent - on top-level revert (depth==0, err != nil): - evm.executionStateGas = 0 Tx finalize (TxnExecutor.Execute): blockStateGas = intrinsicStateGas + evm.executionStateGas @@ -124,7 +134,7 @@ So the walk's "first createObjectChange/codeChange per address" rule is spec-cor ## Found test challenges -After implementing the plan and running `TestExecutionSpecBlockchainDevnet` from `execution/tests/eest_devnet/`, **1,857 of 15,429 subtests fail** (~12%). The implementation is consistent with EIP-8037 PR 11573, but the EEST fixtures pre-date PR 11573 and were generated against the old opcode-time state-gas-charging semantics. +After implementing the plan and running `TestExecutionSpecBlockchainDevnet` from `execution/tests/eest_devnet/`, **1,845 of 15,429 subtests fail** (~12%). The implementation is consistent with EIP-8037 PR 11573, but the EEST fixtures pre-date PR 11573 and were generated against the old opcode-time state-gas-charging semantics. (Initial run before adding the revert-time state-gas restoration was 1,857; the restoration fix moved 12 tests from FAIL to PASS.) The fixture submodule at `execution/tests/execution-spec-tests` is pinned to commit `6c2af7fc95d1c0aa781898b1a7ad78769a536d7f` ("add snolbal-devnet-4 fixtures with static cpsb"), which is from **before** PR 11573's "fixed CPSB + frame accounting" rewrite. As a result the fixtures expect: @@ -179,7 +189,7 @@ Common failure mode across all categories: the fixture's expected `gasUsed` refl ### Recommended next step -Per the `erigon-implement-eip` skill ("question the tests — do not silently fix them"): the EEST fixtures need to be regenerated against the PR-11573-aligned python-spec implementation. Until that lands upstream, the 1,857 Amsterdam-EIP fixture failures are expected and should be documented in the PR description rather than worked around in the implementation. +Per the `erigon-implement-eip` skill ("question the tests — do not silently fix them"): the EEST fixtures need to be regenerated against the PR-11573-aligned python-spec implementation. Until that lands upstream, the 1,845 Amsterdam-EIP fixture failures are expected and should be documented in the PR description rather than worked around in the implementation. If a sooner check is needed, options are: 1. Wait for upstream EEST regeneration against PR 11573 and re-pin the submodule. @@ -335,6 +345,9 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - `opSelfdestruct6780` (lines 1341–1385): no state-gas changes needed in the opcode itself; the gas table changes above are sufficient. - `opCall`/`opCallCode`/`opDelegateCall`/`opStaticCall`: no signature changes. The CallStipend regular-gas correction at lines 1126–1128 / 1177–1179 stays as-is. +### `execution/vm/interpreter.go` +- `useMdGas` for `mdgas.StateGas`: make the spillover-into-`Regular` path **transactional**. If neither `State` alone nor `State + spilled-Regular` can cover the requested amount, return `(initial, false)` **without mutating** `initial.State` (don't pre-zero it). This is required so that the revert-time restoration in `evm.call()`/`evm.create()` (which adds `committedChildBytes × cpsb` back to `gas.State`) reflects the correct pre-charge state. Without this fix, a failed-charge attempt would silently lose `State` gas during the half-applied deduction, causing the parent's reservoir to be under-restored on the subsequent revert. + ### `execution/protocol/txn_executor.go` - No need to communicate the top-level contract address — `evm.create()` derives it from `depth == 0` and the local `address` parameter at commit time. - No need to set a `chargeStateGas` flag — the walk is gated on `IsAmsterdam && !RestoreState` only. diff --git a/execution/vm/evm.go b/execution/vm/evm.go index 3efcc5413b9..b14da39727f 100644 --- a/execution/vm/evm.go +++ b/execution/vm/evm.go @@ -492,12 +492,26 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts // when we're in Homestead this also counts for code storage gas errors. if err != nil || evm.config.RestoreState { if !frameAccumulatorPopped { - evm.popFrameAccumulator() + poppedChildBytes := evm.popFrameAccumulator() + // EIP-8037: on child revert or exceptional halt, all state gas + // consumed by committed descendants — both from the reservoir and + // any that spilled into gas_left — is restored to this frame's + // reservoir (which is then propagated to the parent via + // restoreChildGas). Skip in RestoreState mode (caller discards + // results anyway). At depth==0 this also naturally zeroes + // evm.executionStateGas because the top frame's accumulator holds + // the sum of all charged bytes for the tx. + if !evm.config.RestoreState && evm.chainRules.IsAmsterdam && poppedChildBytes > 0 { + restoreGas := poppedChildBytes * evm.Context.CostPerStateByte + gas.State += restoreGas + if restoreGas > evm.executionStateGas { + evm.executionStateGas = 0 + } else { + evm.executionStateGas -= restoreGas + } + } } evm.handleFrameRevert(&gas, err, depth, snapshot) - // On top-level revert, evm.executionStateGas keeps its accumulated value - // so TxnExecutor can subtract it from the receipt gas. Block-level - // accounting uses 0 explicitly when vmerr != nil (TxnExecutor decision). } else if !frameAccumulatorPopped { // Pre-Amsterdam: no state-gas charging happens in this branch; pop the // accumulator without propagating. @@ -762,12 +776,21 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem // when we're in Homestead, this also counts for code storage gas errors. if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { if !frameAccumulatorPopped { - evm.popFrameAccumulator() + poppedChildBytes := evm.popFrameAccumulator() + // EIP-8037: on child revert or exceptional halt, restore all state + // gas consumed by committed descendants to this frame's reservoir. + // See evm.call() for the full rationale. + if !evm.config.RestoreState && evm.chainRules.IsAmsterdam && poppedChildBytes > 0 { + restoreGas := poppedChildBytes * evm.Context.CostPerStateByte + gasRemaining.State += restoreGas + if restoreGas > evm.executionStateGas { + evm.executionStateGas = 0 + } else { + evm.executionStateGas -= restoreGas + } + } } evm.handleFrameRevert(&gasRemaining, err, depth, snapshot) - // On top-level revert, evm.executionStateGas keeps its accumulated value - // so TxnExecutor can subtract it from the receipt gas. Block-level - // accounting uses 0 explicitly when vmerr != nil (TxnExecutor decision). } else if !frameAccumulatorPopped { // Pre-Amsterdam or RestoreState: pop the accumulator without // propagating (no state-gas charging happens in this branch). diff --git a/execution/vm/interpreter.go b/execution/vm/interpreter.go index 741ee8c22de..cf453078216 100644 --- a/execution/vm/interpreter.go +++ b/execution/vm/interpreter.go @@ -186,14 +186,21 @@ func useMdGas(evm *EVM, initial mdgas.MdGas, gas uint64, t mdgas.MdGasType, trac } return initial, true } - // otherwise use up all remaining state gas and try to use some from the regular gas - gas = gas - initial.State + // State alone is insufficient. Check if spilling into Regular can + // cover the remainder before mutating anything — useMdGas must be + // transactional so that on insufficient gas the frame's reservoir + // is left untouched (so revert-time restoration of consumed-by- + // children state gas works correctly per EIP-8037). + needFromRegular := gas - initial.State + if initial.Regular < needFromRegular { + return initial, false + } initial.State = 0 - initial.Regular, ok = useGas(initial.Regular, gas, tracer, reason) - if ok && evm != nil { + initial.Regular, _ = useGas(initial.Regular, needFromRegular, tracer, reason) + if evm != nil { evm.executionStateGas += originalGas } - return initial, ok + return initial, true case mdgas.RegularGas: initial.Regular, ok = useGas(initial.Regular, gas, tracer, reason) if ok && evm != nil { From c80263b2bc8af30c7927a4dd8472c577a40d66a0 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Tue, 28 Apr 2026 21:12:15 +0000 Subject: [PATCH 27/31] cleanup TxnExecutor.Execute --- execution/protocol/txn_executor.go | 35 ++---------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index 0df00eeec2a..4aa838ad526 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -613,40 +613,9 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. refundQuotient = params.RefundQuotientEIP3529 } if rules.IsAmsterdam { - // EIP-8037: per-frame state-gas accounting is journal-walk based and - // runs inside the EVM at each frame's commit. The top-frame walk - // applies the EIP-6780 selfdestruct filter, so create+destroy in - // same tx (any nesting) naturally produces a negative top-frame - // delta that credits the over-charge back. No TxnExecutor-level - // refund needed. evm.ExecutionStateGas() reflects the net charges. - // - // Block-level state gas: zero on top-level error (state didn't grow); - // otherwise intrinsic + executionStateGas. - executionStateGas := st.evm.ExecutionStateGas() - blockExecutionStateGas := executionStateGas - if vmerr != nil { - blockExecutionStateGas = 0 - } - blockState := imdGas.State + blockExecutionStateGas - blockRegular := imdGas.Regular + st.evm.RegularGasConsumed() - st.blockRegularGasUsed = max(blockRegular, intrinsicGasResult.FloorGasCost) - st.blockStateGasUsed = blockState - // Receipt gasUsed: tx.gas - gas_left - reservoir. Use Total()-level - // subtraction to avoid per-component uint64 underflow when - // gasRemaining.State > initialGas.State (reservoir grew via child - // reverts/credits). + st.blockRegularGasUsed = max(imdGas.Regular+st.evm.RegularGasConsumed(), intrinsicGasResult.FloorGasCost) + st.blockStateGasUsed = imdGas.State + st.evm.ExecutionStateGas() st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() - if vmerr != nil { - // Top-level error: per spec, execution state gas is refunded - // to the reservoir (`state_gas_left += state_gas_used; - // state_gas_used = 0`). The actual charges are still in - // gasRemaining (sub-frame charges decremented gas.State, never - // restored), so subtract them explicitly here. - st.txnGasUsedB4Refunds -= executionStateGas - } - // Only the regular-gas refund flows through the 20%-capped - // refund_counter. State-gas charges/credits are already netted - // inside evm.executionStateGas via per-frame deltas. refund := min(st.txnGasUsedB4Refunds/refundQuotient, st.state.GetRefund().Regular) st.txnGasUsed = max(intrinsicGasResult.FloorGasCost, st.txnGasUsedB4Refunds-refund) } else if rules.IsPrague { From 24556a13b712c7577cbb6d4315cf1265e3e3a68f Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:47:15 +0000 Subject: [PATCH 28/31] update plan --- .../20260427-eip8037-journal-state-gas-spec.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index f023b337574..6f382a0fd3a 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -420,5 +420,20 @@ Same short-circuit as Case A: `originalValue=X` non-zero never triggers the new- - `make test-short` passes. - `make test-all` passes. +### Alignment with Mario's TODO test list + +The page at https://notes.ethereum.org/@marioevz/eip-8037-todo-tests lists three test cases that the EIP-8037 implementation must satisfy. All three are handled correctly by the journal-walk algorithm without code changes: + +1. **"TX spending all balance from an account should not refund account destroy refund"** — A pre-existing account whose entire balance is drained by a tx must NOT trigger a 112×cpsb refund (it was never charged in the first place). In our walk: only `createObjectChange`, `resetObjectChange`, `codeChange`, and `storageChange` contribute bytes. `balanceChange` entries are skipped, and the SELFDESTRUCT filter only fires on accounts with `.newlyCreated && .selfdestructed` — a pre-existing account drained to 0 does not match that predicate, so the top-frame walk does not skip it (and there's nothing to skip, since no `createObjectChange` was emitted for it). Net effect: 0 charge, 0 refund. + +2. **"Contract creation -> return successfully -> gas to sstore"** — A top-level CREATE tx that deploys code AND writes a storage slot. Covered by edge case #43: the intrinsic charges 112×cpsb (account); the top-frame walk uses `excludeCreate=C` to skip the contract's `createObjectChange` (avoiding double-count); `codeChange` contributes `len(code)` bytes; `storageChange` contributes 32 bytes. Total state gas: `112×cpsb (intrinsic) + (len(code) + 32)×cpsb (execution)`. + +3. **"Not enough gas to pay for account creation at the end of the tx, but still got refund from a self-destruct"** — A frame creates account A and then SELFDESTRUCTs A within the same tx; even if the test runs near OOG limits, the create + self-destruct pair must net to 0 state gas. Covered by edge cases #30, #31, #34, #35–40: the deepest creator frame charges 112+code+slots at its commit (no filter); the top-frame walk applies the filter, sees A is `.newlyCreated && .selfdestructed`, and skips it; this makes `top.walkTotal < top.committedChildBytes`, producing a negative delta that credits the over-charge back. Per-address dedup at the walk level ensures the credit fires exactly once even if A is SELFDESTRUCTed multiple times. + +The four design questions on Mario's page are also resolved by the implementation: +- **Which frame is charged for account creation?** The frame in which `createObjectChange` lands. For `CALL` to non-existent address with value, that's the called frame (snapshot is pushed before `CreateAccount`). For top-level CREATE tx, the contract account is intrinsic-charged (with `excludeCreate` to prevent double-count). For nested `CREATE`, the create-frame charges via its own `createObjectChange`. +- **Code deposit charge frame?** Always the create-frame, because `codeChange` is emitted by the create-frame's `SetCode` call right before commit. +- **Double-charging via intrinsic_state_cost?** No — `excludeCreate` parameter to `ComputeFrameStateBytes` skips the top-level CREATE's contract account from the walk. + ## Rollout All changes are gated on `chainRules.IsAmsterdam` — pre-Amsterdam paths are untouched. Cutover is automatic at the fork-transition block, exercising both code paths in a single devnet run. From 4ebc93fe17adce0023e3b7341328f895a6153697 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:51:59 +0000 Subject: [PATCH 29/31] update fixtures to fixtures_snobal-devnet-5 --- execution/tests/execution-spec-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution/tests/execution-spec-tests b/execution/tests/execution-spec-tests index 6c2af7fc95d..b1241d947fd 160000 --- a/execution/tests/execution-spec-tests +++ b/execution/tests/execution-spec-tests @@ -1 +1 @@ -Subproject commit 6c2af7fc95d1c0aa781898b1a7ad78769a536d7f +Subproject commit b1241d947fdd611ddce263d3c70cf12b987df68f From 3284f2118de33034d3be869fc91cb02e1f87449d Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Wed, 29 Apr 2026 19:37:18 +0000 Subject: [PATCH 30/31] update plan --- ...20260427-eip8037-journal-state-gas-spec.md | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index 6f382a0fd3a..57ce23a5ae3 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -132,6 +132,249 @@ For **accounts and code**, no equivalent tx-entry field is needed because the EV So the walk's "first createObjectChange/codeChange per address" rule is spec-correct without any tx-entry comparison. No remaining divergence. +### Recent spec changes (PR 11573 commits after `d2a0230`) + +The PR has continued to evolve. Each commit and its impact on our plan: + +| Commit | Subject | Impact on plan | +|---|---|---| +| `3f190787` | Add gas used rules | None — formalises `execution_regular_gas_used` and `execution_state_gas_used` as per-tx counters that increase on charges and decrease on refunds. Already matches our `evm.regularGasConsumed` and `evm.executionStateGas`. | +| `b8193df` | Fix errors in eip | Two adjustments: (a) cleared-slot regular-gas refund of `GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS = 2800` to `refund_counter` (we already emit `SstoreSetGasEIP8037 - WarmStorageReadCostEIP2929 = 2900-100 = 2800` ✓); (b) **EIP-7702 auth refund now also decreases `execution_state_gas_used`** — required code update, see below. Also clarifies system-tx gas formula and CREATE2 hashing cost (both already aligned). | +| `8daeab6` | Fix gas used error | None — `execution_*_gas_used` initialised to 0, not to intrinsic. Already matches. | +| `421279b` | Fix additional errors | None — receipt = `tx_gas_used` post-refund post-floor (matches); CPSB now formally a "fixed parameter" (matches); EIP-7825 contract-size limit applies "when CPSB = 1174". | +| `46faf2a` | Jochem's review | None — wording. | +| `3535f03` | Small fixes from Jochem review | None — `requires:` list updated to `2780, 6780, 7702, 7825, 7976, 7981, 8038`; SELFDESTRUCT explicitly aligned with EIP-6780 (matches). | +| `731a276` | Add ERC-4337 interaction | None on consensus. New informational subsection: bundlers/EntryPoint must account for state-gas explicitly because `GAS` opcode returns `gas_left` only and cannot observe `state_gas_reservoir`. Cross-user-operation subsidy risk noted. **No execution-client behavior change** — purely a recommendation for ERC-4337 implementations layered on top. | + +**Required code change (commit `b8193df`)**: Spec now states "`execution_state_gas_used` decreases by the corresponding amount" when an EIP-7702 authority is non-empty. Previously our impl added the 112×cpsb refund to `gas_remaining.State` (reservoir replenish) but left `blockStateGasUsed = imdGas.State + executionStateGas` at the worst-case value. Per the new spec, `block_state_gas_used` must also drop by `stateIgasRefund`. Fix applied in `execution/protocol/txn_executor.go` at the Amsterdam refund branch and the gasBailout/no-refund Amsterdam branch: +```go +st.blockStateGasUsed = imdGas.State + st.evm.ExecutionStateGas() - stateIgasRefund +``` +Subtraction is safe: by construction `stateIgasRefund = 112 × cpsb × num_existing_auths` and `imdGas.State ≥ 135 × cpsb × num_auths ≥ stateIgasRefund`. + +**Open question (commit `b8193df`)**: The spec text says the state-gas refund happens "in parallel with EIP-7702's `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` regular-gas refund", suggesting both refunds fire under Amsterdam. Under our current impl the regular-gas refund is only applied pre-Amsterdam (the `else` branch of `verifyAuthorities`). With Amsterdam values (`PER_AUTH_BASE_COST_EIP8037 = 7500`, intrinsic regular per auth = 7500), refunding `25000 - 12500 = 12500` regular gas would exceed the per-auth charge, producing negative net regular cost. This appears to be ambiguous spec wording rather than intended behavior — leaving the impl unchanged on this point pending clarification with spec authors. + +## Test status with `snobal-devnet-5` fixtures + +After the fixture submodule was bumped to `snobal-devnet-5` (the user's manual update aimed at the latest spec), running `TestExecutionSpecBlockchainDevnet` with `-count=1`: + +| Stage | File-level failures | Notes | +|---|---|---| +| Initial (after fixture bump) | 146 | Mostly cross-fork tests (Prague/Cancun/etc.) running under Amsterdam | +| After empty-account skip in `liveAccount` | 52 | Net improvement of 94 file-level (1+ k sub-test) failures | + +**Empty-account skip** added in `IntraBlockState.ComputeFrameStateBytes` `liveAccount`: +```go +if applyFilter && so.data.Empty() { + return so, false +} +``` +Rationale: at top-frame walk, an account that is empty per EIP-161 (`nonce==0 && balance==0 && codeHash==EmptyCodeHash`) will be pruned at tx finalize, so it must not be counted as a created account. This catches `AddBalance(X, 0)` to a non-existent X, which TouchAccount-creates an empty stateObject that emits `createObjectChange{X}` even though no real new account persists. + +### Remaining 52 file-level failures (post-fix) + +| Category | Count | Description | +|---|---|---| +| `amsterdam/eip8037` | 16 | Direct EIP-8037 spec tests (state-gas accounting edge cases) | +| `cancun/eip6780_selfdestruct` | 12 | EIP-6780 same-tx selfdestruct | +| `frontier/opcodes` | 3 | Basic opcode gas under Amsterdam | +| `amsterdam/eip7928` | 3 | Block Access List interactions | +| Other | 18 | spread across many forks/EIPs | + +**Recurring failure pattern (eip8037)**: state-gas off by exactly `32×CPSB` (one storage slot) or `112×CPSB` (one new account). Sample: `sstore_restoration_sub_frame_revert[CALL]` gives 76,137 vs expected 38,570 (diff = 37,567 ≈ 32×CPSB). + +**Root cause: chargeFrameStateGas OOG burned gas as exceptional halt.** When the frame-end state-gas charge can't cover (reservoir + remaining regular gas < required), our impl returned `ErrOutOfGas` which routes through `handleFrameRevert`'s exceptional-halt path — burning the frame's remaining `gas.Regular` and adding it to `evm.regularGasConsumed`. That's where the spurious +37,567 came from. + +**EELS reference behavior** (verified by tracing `tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py::test_sstore_restoration_sub_frame_revert` in `ethereum/execution-specs@devnets/snobal/5`): +- `apply_frame_state_gas` (`vm/interpreter.py`) sets `evm.error = OutOfGasError()` directly without raising `ExceptionalHalt`. +- Process_message's `try/except` only matches `ExceptionalHalt` for the gas-burn path. So apply_frame_state_gas's OOG bypasses it — `evm.gas_left` is preserved. +- `incorporate_child_on_error` returns the child's `gas_left` to the parent reservoir. +- Net: a frame-state-gas OOG behaves like a REVERT (state rolled back, gas returned to parent), NOT like a true exceptional halt. + +**Fix applied** in `execution/vm/`: +- New error: `ErrFrameStateGasOOG` (in `errors.go`). +- `chargeFrameStateGas` returns `ErrFrameStateGasOOG` (was `ErrOutOfGas`). +- `handleFrameRevert` treats `ErrFrameStateGasOOG` like `ErrExecutionReverted` — preserves `gas.Regular`, no burn into `regularGasConsumed`. + +**Test impact**: `TestExecutionSpecBlockchainDevnet` cache-busted: 52 → **31 file-level failures**. EIP-8037 direct scope: 16 → **2 failures**. 14 EIP-8037 tests fixed by this single change. Cancun/EIP-6780 selfdestruct cluster (12 tests) and Frontier/Constantinople tests (~10 tests) still failing — those have a different root cause (likely related to selfdestruct gas accounting, separate investigation). + +### Fix #2: excludeCreate refund for same-tx CREATE+SELFDESTRUCT + +**Test traced**: `test_selfdestruct_in_create_tx_initcode` (top-level CREATE tx where initcode SELFDESTRUCTs to a fresh beneficiary). Expected `gas_used = 131,488`; our impl produced `262,976` (extra 112×CPSB). + +**Root cause via EELS comparison**: +- Intrinsic charges 112×CPSB for the contract address. +- EELS's `compute_state_byte_diff` at frame end gives `byte_delta = +112` (only the SELFDESTRUCT beneficiary; the contract is `existed_at_frame_entry=true` since the snapshot was taken AFTER `move_ether` and `mark_account_created`). +- EELS's `process_message_call` then refunds 112×CPSB (and code bytes if any) for accounts in `accounts_to_delete ∩ created_accounts`. Net execution_state = 0. +- Our impl: walk excludes the contract via `excludeCreate` and counts the beneficiary `+112`. No corresponding refund mechanism for the contract → over-counts. + +**Fix applied** in `IntraBlockState.ComputeFrameStateBytes` and `evm.chargeFrameStateGas`: +- New `excludeCreateRefund` second return value from `ComputeFrameStateBytes`. When `applyFilter && excludeCreate ≠ NilAddress` and the contract is `newlyCreated && selfdestructed`, returns 112 (+ len(code) if present) — mirroring EELS's `accounts_to_delete` refund. +- `chargeFrameStateGas` subtracts `excludeCreateRefund` from the frame's positive delta; if the result is negative it credits back. + +**Test impact**: 31 → **30 file-level failures**. EIP-8037 direct scope: 2 → **1 failure**. Remaining EIP-8037 case (`sstore_restoration_charge_in_ancestor`) is now a *receipt hash mismatch* (not gas-used), indicating receipt field divergence in CALLCODE/DELEGATECALL variants — separate issue from gas accounting. + +### Remaining 30 failures — need per-cluster investigation + +Three fixes brought 146 → 30. The remaining 30 cluster as follows: + +| Cluster | Tests | Pattern | Status | +|---|---|---|---| +| `cancun/eip6780_selfdestruct/*` | 12 | Diff = 1×112×CPSB (or other). **Verified test-isolation bug**: each failing variant PASSES when run truly alone (single `-run` regex), but FAILS when sibling tests in the same fixture file run first. Confirmed for `selfdestruct_not_created_in_same_tx_with_revert.json` — variant 2 fails but variants 1, 2, 3 individually all pass. The cross-contamination is at the subtest level (`t.Run` within a single fixture file), and persists even with `-parallel 1`. The 81-byte SD code our impl shows in failing runs (with CREATE at pc=42, deploying 5 phantom contracts) is NOT the SD contract code for the failing test — it matches `test_recursive_contract_creation_and_selfdestruct`'s SD code. Each subtest creates a fresh `ExecModuleTester` (own tmpdir DB) but some package-level state (sync.Pool? cache?) leaks between them. **Defensive reset of `stateObjectPool` returned objects did NOT fix the bug.** | **Not fixed**. Needs deeper investigation of which package-level state persists between subtests. | +| `frontier/opcodes` + `create` | 5 | Various opcode/create gas patterns under Amsterdam fork. Different from cancun pattern. | Untraced | +| `prague/{6110,7002,7251}` | 3 | System requests (deposits, withdrawals, consolidations). | Untraced | +| `tangerine_whistle/eip150_selfdestruct` | 2 | Diff varies; `gas used 25943, header 37568` (under-counts) AND `gas used 157316, header 37803` (over-counts). Mixed pattern. | Untraced | +| `constantinople/eip1052_extcodehash` | 2 | Diff = 131488 (1×112×CPSB) and 135010 (115×CPSB, irregular). | Untraced | +| `amsterdam/eip8037 sstore_restoration_charge_in_ancestor` | 1 | Receipt hash mismatch (not gas), CALLCODE/DELEGATECALL variants. | Untraced | +| `amsterdam/eip7708 selfdestruct_to_system_address` | 1 | Selfdestruct to system address (`0xff...fe`). | Untraced | +| Others | 4 | byzantium staticcall, cancun create, shanghai warm_coinbase, frontier scenarios | Untraced | + +Each cluster needs: +1. Generate EELS trace via `cd /tmp/execution-specs && uv run fill -v --fork Amsterdam --traces --evm-dump-dir=/tmp/traces`. +2. Compare `result.json`'s gasUsed with our impl's output. +3. Identify which addresses/storage events EELS counts vs ours. +4. Fix root cause in walk or chargeFrameStateGas. + +The tooling is set up at `/tmp/execution-specs` (`devnets/snobal/5` branch) with `uv` deps installed. Each trace iteration takes ~30 seconds. + +## Session end state + +- **146 → 19 file-level failures** (87% reduction). +- EIP-8037 direct scope: **17 → 1 failure** (`sstore_restoration_charge_in_ancestor`, receipt hash mismatch — separate from gas accounting). +- 5 fixes applied, all backed by EELS reference-impl traces. +- `make lint` clean. + +### Fix #4: resetObjectChange does not contribute +112 to walk total + +**Root cause** (verified against EELS `compute_state_byte_diff` in +`forks/amsterdam/state_tracker.py`): EELS only adds +112 for an account when +`account_now != None && !existed_at_frame_entry && !existed_at_tx_entry`. A +pre-existing account being deployed to (Erigon's `resetObjectChange`) fails +the third condition — the account record already counted toward block-state +at the prior funding tx, so the new CREATE's frame-end byte_delta does NOT +re-charge 112. + +**Fix applied** in `IntraBlockState.ComputeFrameStateBytes`: +- `resetObjectChange` does NOT add +112 to `total` (only `createObjectChange` + does — that's the `account didn't exist at tx entry` case). +- The address is still tracked in `acctData` so the EIP-6780 refund path + (see fix #5) refunds 112 unconditionally for `accounts_to_delete ∩ + created_accounts`. + +### Fix #5: explicit per-tx EIP-6780 SELFDESTRUCT refund at top frame + +**Root cause** (verified by EELS trace of +`test_create_selfdestruct_same_tx[selfdestruct_contract_initial_balance_100000-single_call-CREATE]`): +EELS's `compute_state_byte_diff` at top-frame end charges +112 for D ONLY +in balance_0 (D fresh). For balance_100000 (D existed at tx entry), no +112 +is charged. EELS THEN refunds 112 + len(code) + non_zero_storage_bytes +unconditionally for any address in `accounts_to_delete ∩ created_accounts` +(via `process_message_call`'s top-level refund loop). This produces: + + | balance | top-frame +112 for D | refund 181 for D | net for D | + |---------|----------------------|-------------------|-----------| + | 0 | yes (charged) | yes (refunded) | 0 | + | 100_000 | no | yes (-112 offset) | -112 | + +The -112 in balance_100000 offsets some other charge (e.g., the intrinsic +112 for the top-level CREATE contract C), giving the test's expected +`block_state_gas_used = max(0, intrinsic + execution) = max(0, 112 + (-112) + +slots) = (slots) bytes`. + +Our previous walk applied an EIP-6780 filter at the top frame (skipping +walk entries for `newlyCreated && selfdestructed` addresses). That filter +worked for balance_0 (D's bytes effectively cancelled in walk total) but +NOT for balance_100000, because the walk-and-delta math always sums to the +top-frame walkTotal — and with the filter, both cases produced the same +total. We needed to drive net balance_100000 NEGATIVE. + +**Fix applied**: +1. `ComputeFrameStateBytes` now returns `(total, accountRefund)`. Total no + longer applies the EIP-6780 filter — it counts everything per the rules + (createObjectChange, codeChange, storageChange). +2. At top frame (`applyFilter`), `accountRefund` sums per-address + `accountBytes + codeBytes + storageBytes` for each address in + `newlyCreated && selfdestructed`. This mirrors EELS's + `accounts_to_delete ∩ created_accounts` refund. +3. `chargeFrameStateGas` subtracts `accountRefund` from delta: + `delta = walkTotal - childTotal - accountRefund`. Negative delta credits + `gas.State` and decrements `evm.executionStateGas`. +4. `excludeCreate` (top-level CREATE C's address) is pre-tracked in `acctData` + with `accountBytes=112` because C's `createObjectChange` lands BEFORE the + top frame's `frameStart` (the snapshot is pushed AFTER `CreateAccount`) + and so isn't seen by the walk. Without pre-tracking, the unified refund + couldn't refund C. + +### Fix #6: signed `executionStateGas` — allow negative for refund offset + +**Root cause**: with the new explicit-refund mechanism (fix #5), the credit +at top frame can exceed the per-tx running `executionStateGas` total. Under +the old `uint64` semantics, the credit saturated at 0, leaving the +intrinsic-only block-state-gas charged. EELS's `state_gas_used` is signed +(can go negative), and `tx_state_gas = max(0, intrinsic_state_gas + +state_gas_used)` clamps at the block-level uint64 counter. + +**Fix applied** in `execution/vm/evm.go`: +- `evm.executionStateGas` field changed from `uint64` to `int64`. +- `ExecutionStateGas()` now returns `int64`. +- Credit / restore paths in `chargeFrameStateGas` and `handleFrameRevert` + no longer saturate at 0 — they subtract `int64(creditGas)` directly. +- `useMdGas` casts `originalGas` to `int64` when adding. +- `txn_executor.go`'s `blockStateGasUsed` calc now does `max(0, int64(imdGas.State) + + executionStateGas)` before assigning to the `uint64` block counter. + +### Fixes summary table + +| Fix | What | Tests fixed | +|---|---|---| +| 1 (earlier) | empty-account skip in `liveAccount` | ~94 file-level | +| 2 (earlier) | `ErrFrameStateGasOOG` (soft OOG) | 14 EIP-8037 tests | +| 3 (earlier) | excludeCreateRefund (initcode SELFDESTRUCT) | 1 EIP-8037 | +| 4 (this session) | resetObjectChange → no +112 in total | balance_100000 cancun cluster | +| 5 (this session) | unified EIP-6780 refund at top frame | most of cancun cluster | +| 6 (this session) | int64 executionStateGas | enables fix #5's negative offset | + +Result: 146 → 19 file-level failures (87% reduction). + +### Remaining 19 file-level failures — categorized + +**Gas-accounting under/over-charge by ~112 bytes** (1 missed account creation): +- `cancun/eip6780_selfdestruct/selfdestruct_revert/*` (2 files) — under-charge 112. Pattern: a beneficiary address gets touchAccount'd (not createObjectChange) by SELFDESTRUCT to an `pre.fund_eoa(amount=0)` recipient. Erigon's TouchAccount path doesn't emit `createObjectChange` for the address even though EELS treats it as a new account at frame end (existed_at_tx_entry=false in pre-state). Walk misses +112. +- `frontier/opcodes/*`, `frontier/scenarios/*` (4 files) — same +112 under-count pattern. + +**Receipt hash mismatch** (not gas-related): +- `amsterdam/eip8037 sstore_restoration_charge_in_ancestor` (CALLCODE/DELEGATECALL receipt fields). +- `cancun/create/create_oog_from_eoa_refunds` — receipt hash mismatch on multiple subtests. +- `cancun/eip6780_selfdestruct/recursive_contract_creation_and_selfdestruct` — receipt hash on `recursion_depth_3` only. +- `cancun/eip6780_selfdestruct/recreate_self_destructed_contract_different_txs` — same pattern. + +**System contract / under-charge by larger amounts**: +- `prague/eip6110_deposits/deposit` — under-charge by 144 bytes (1 account + 1 slot) +- `prague/eip7002_el_triggerable_withdrawals/withdrawal_requests` — under-charge by 960 bytes (large) +- `prague/eip7251_consolidations/consolidation_requests` — similar pattern + +**Other edge cases**: +- `amsterdam/eip7708 selfdestruct_to_system_address` — under-charge to 0 execution_state. SELFDESTRUCT to 0xff…fe (system address) needs special handling. +- `byzantium/eip214_staticcall` — only `precompile_0x08-zero_value` variant; STATICCALL to BN256_PAIRING with value=0. +- `constantinople/eip1052 extcodehash_created_and_deleted` — CALL variant, over-charge 115 bytes (irregular). +- `tangerine_whistle/eip150_selfdestruct/*` (2 files) — exact_gas variants where minor accounting drift fails strict OOG limits. +- `shanghai/eip3651_warm_coinbase warm_coinbase_call_out_of_gas` — under-charge 9.9 bytes (irregular). + +### Common root cause for the +112 under-charge family + +Erigon's `AddBalance(addr, amount)` path: +- For `amount.IsZero()` → `TouchAccount` → `GetOrNewStateObject` (which emits `createObjectChange`) AND a `touchAccount` journal entry. +- For non-zero amount → `GetOrNewStateObject` directly → emits `createObjectChange` if account didn't exist. + +So in either path, `createObjectChange` SHOULD fire when the account first appears. The journal trace for the failing test shows `touchAccount` at the address but NO `createObjectChange`. This suggests the address was "resolved" by a prior read (e.g., access list addition) which loaded a stateObject, and the TouchAccount inside AddBalance found it pre-existing. + +Possible fix paths (none implemented this session): +1. Track per-address state in walk: account in `account_writes` with `account_now != None && !existed_at_frame_entry && !existed_at_tx_entry` → +112 bytes. Requires journal-walk to recognize "this address became non-empty during this tx" via balanceChange/touchAccount, not just createObjectChange. +2. Mirror Erigon's `nilAccounts + balanceInc` mechanism in walk: if address was loaded as nil pre-tx but ended up with non-zero balance/code, count +112. +3. Generate fixtures from updated EELS spec (already on snobal-devnet-5; no further fixture updates expected). + +Each remaining cluster needs per-test EELS trace comparison. Investigation tooling at `/tmp/execution-specs` (devnets/snobal/5 branch). + ## Found test challenges After implementing the plan and running `TestExecutionSpecBlockchainDevnet` from `execution/tests/eest_devnet/`, **1,845 of 15,429 subtests fail** (~12%). The implementation is consistent with EIP-8037 PR 11573, but the EEST fixtures pre-date PR 11573 and were generated against the old opcode-time state-gas-charging semantics. (Initial run before adding the revert-time state-gas restoration was 1,857; the restoration fix moved 12 tests from FAIL to PASS.) From 23d4c81f4c9395e28cf0c7bc09f2b20993d19ec7 Mon Sep 17 00:00:00 2001 From: taratorio <94537774+taratorio@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:38:55 +0000 Subject: [PATCH 31/31] test fixes --- ...20260427-eip8037-journal-state-gas-spec.md | 108 +++++++--- execution/protocol/txn_executor.go | 47 ++++- execution/state/intra_block_state.go | 188 ++++++++++++++---- execution/state/journal.go | 11 + execution/vm/errors.go | 7 + execution/vm/evm.go | 145 +++++++++----- execution/vm/interpreter.go | 4 +- 7 files changed, 385 insertions(+), 125 deletions(-) diff --git a/docs/plans/20260427-eip8037-journal-state-gas-spec.md b/docs/plans/20260427-eip8037-journal-state-gas-spec.md index 57ce23a5ae3..30669cd4e5c 100644 --- a/docs/plans/20260427-eip8037-journal-state-gas-spec.md +++ b/docs/plans/20260427-eip8037-journal-state-gas-spec.md @@ -323,6 +323,45 @@ state_gas_used)` clamps at the block-level uint64 counter. - `txn_executor.go`'s `blockStateGasUsed` calc now does `max(0, int64(imdGas.State) + executionStateGas)` before assigning to the `uint64` block counter. +### Fix #7: nullify `versionedWrites[AddressPath]` on `createObjectChange.revert` (2026-04-29) + +**Root cause** (verified by tracing +`tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py::test_selfdestruct_created_in_same_tx_with_revert[outer_selfdestruct_after_inner_call-same_tx]`): + +When `createObjectChange{addr}` is reverted (frame revert pops it from +the journal), `revert()` removes `addr` from `sdb.stateObjects` but +leaves the `versionedWrites[addr][AddressPath]` entry that +`createObject()` emitted via `versionWritten`. A subsequent same-tx +read of `addr` via `getStateObject → getVersionedAccount → versionedRead` +finds the stale `WriteSetRead` value (an empty `*accounts.Account`), +treats `addr` as existing, and calls `stateObjectForAccount` which +re-adds `addr` to `stateObjects` *without* firing a new +`createObjectChange`. The next non-reverted SELFDESTRUCT to that addr +then writes a `balanceChange` with `wasCommited=false` and tags the +stateObject as `newlyCreated=false`. Walk misses the +112 byte charge +even though EELS counts the address as freshly created at frame end. + +**Symptom**: under-charge by exactly 112 bytes × CPSB on a family of +selfdestruct-with-revert tests (~130 file-level failures across the +suite, but only the cancun selfdestruct_revert pair shows the 112-byte +diff cleanly; the rest exhibit downstream effects on the +versionedWrites cleanup at finalize). + +**Fix applied** in `execution/state/journal.go`, +`createObjectChange.revert()`: +```go +if s.versionMap != nil { + s.versionedWrites.UpdateVal(ch.account, AccountKey{Path: AddressPath}, (*accounts.Account)(nil)) +} +``` +Nullifying (not deleting) preserves the entry in `versionedWrites` so +that `MakeWriteSet`'s parallel-mode cleanup loop (line 2304) still +sees the address as reverted and clears stale entries from the global +versionMap. The `nil` value causes subsequent versionedRead to return +nil for AddressPath, so `getStateObject` doesn't materialize a phantom +stateObject. CodeHashPath stays as the reverted-empty-account +EmptyCodeHash, which is the correct value for a non-existent address. + ### Fixes summary table | Fix | What | Tests fixed | @@ -330,50 +369,53 @@ state_gas_used)` clamps at the block-level uint64 counter. | 1 (earlier) | empty-account skip in `liveAccount` | ~94 file-level | | 2 (earlier) | `ErrFrameStateGasOOG` (soft OOG) | 14 EIP-8037 tests | | 3 (earlier) | excludeCreateRefund (initcode SELFDESTRUCT) | 1 EIP-8037 | -| 4 (this session) | resetObjectChange → no +112 in total | balance_100000 cancun cluster | -| 5 (this session) | unified EIP-6780 refund at top frame | most of cancun cluster | -| 6 (this session) | int64 executionStateGas | enables fix #5's negative offset | +| 4 (earlier) | resetObjectChange → no +112 in total | balance_100000 cancun cluster | +| 5 (earlier) | unified EIP-6780 refund at top frame | most of cancun cluster | +| 6 (earlier) | int64 executionStateGas | enables fix #5's negative offset | +| 7 (prior session) | nullify versionedWrites[AddressPath] on createObjectChange.revert | ~130 file-level failures across forks | +| 8 (this session) | EIP-161 empty-account filter at ALL frames in walk | frontier under-charges (constant_gas, scenarios, value_transfer); tangerine_whistle eip150 selfdestruct cluster (16); byzantium eip214 staticcall_nested_call_to_precompile; shanghai warm_coinbase; constantinople eip1052; prague system contract under-charges (3 files) | +| 9 (this session) | EIP-8037 cleared-slot rule in walk + signed walkTotal/childTotal + propagate only positive walkTotal | cancun create_oog_from_eoa_refunds (6 receipt-hash); cancun selfdestruct receipt-hash family (3); amsterdam sstore_restoration_charge_in_ancestor (6 receipt-hash) | +| 10 (this session) | clamp underflow when gasRemaining > initialGas (state-gas credits) | enables fix #9 receipt computation; recursive selfdestruct receipt mismatches | +| 11 (this session) | resetObjectChange charges +112 when `prev.original.Empty()` | enables 0xff..fe address creation when system call's TouchAccount-then-prune sequence preceded | +| 12 (this session) | getStateObject treats EIP-161-empty versionMap accounts as non-existent | selfdestruct_to_system_address | + +Result: 146 → **0 file-level failures** (100% reduction). All 16,389 subtests pass. + +### Fixes 8–12 (this session) — root causes and rationale + +**Fix #8: EIP-161 empty-account filter at ALL frames** (`IntraBlockState.ComputeFrameStateBytes` `liveAccount`) + +The fix #1 empty-account skip was guarded by `applyFilter` (top frame only). EELS's `compute_state_byte_diff` applies the filter at every frame: `account_now != None` uses EIP-161 "exists" semantics (empty accounts are None). Removing the `applyFilter &&` guard fixes: + +- STATICCALL to a non-existent address: `evm.call(STATICCALL, …, addr=c0f6dc, …)` does `AddBalance(addr, 0)` to trigger a touch — emits `createObjectChange{c0f6dc}` followed by `touchAccount{c0f6dc}` BEFORE the inner frame's `frameStart`. At the inner frame's commit (depth=1), the walk previously charged +112 for the empty `c0f6dc` (filter off at sub-frames), forcing a state-gas OOG that reverted the parent's effects (e.g., the SSTORE 0→1 that the test was verifying). With the filter at all frames, `c0f6dc` is correctly skipped, the inner frame doesn't OOG, and the SSTORE survives. Fixed `frontier/opcodes/value_transfer_gas_calculation` and the entire frontier/tangerine/shanghai/constantinople/prague-system-contract cluster. + +**Fix #9: EIP-8037 cleared-slot rule + signed walkTotal/childTotal + positive-only propagation** (`IntraBlockState.ComputeFrameStateBytes` storageChange handler; `EVM.committedChildBytes`/`chargeFrameStateGas`/`propagateChildBytes`) + +EELS's `compute_state_byte_diff` returns a SIGNED byte_delta — positive for new state, negative for cleared state (`value_now == 0 && frame_entry != 0 && tx_entry == 0` → −32). Erigon's walk previously returned uint64 and only emitted +32 for new slots; the cleared-slot −32 case wasn't recognised. Three coordinated changes: + +1. `ComputeFrameStateBytes` returns `int64` total. The storageChange handler now also emits `total -= 32` when `originalValue == 0 && prevalue != 0 && current == 0` (the cleared-slot rule). Because `seenSlot` dedups to the FIRST entry per (addr, key) in the segment, `e.prevalue` is the slot's value at frame-entry (for sub-frames) or tx-entry (for top frames). -Result: 146 → 19 file-level failures (87% reduction). +2. `EVM.committedChildBytes` and `propagateChildBytes` use signed `int64` to carry the negative walkTotal up. `chargeFrameStateGas` does signed delta math: `delta = walkTotal − childTotal − accountRefund`; `delta > 0` charges, `delta < 0` credits. -### Remaining 19 file-level failures — categorized +3. `propagateChildBytes` propagates ONLY positive walkTotals to the parent. EELS's `apply_frame_state_gas` updates `state_gas_used` only on positive `this_call_cost`; negative path credits the reservoir but leaves `state_gas_used` unchanged. Mirroring this in Erigon: a child with a negative walkTotal credits its own gas.State (via the `delta < 0` path) and the credit propagates to the parent through the gas return mechanism, but the parent's `committedChildBytes` (= EELS `already_paid`) stays at 0 for that child. Otherwise the parent over-charges by the absolute value of the child's credit. -**Gas-accounting under/over-charge by ~112 bytes** (1 missed account creation): -- `cancun/eip6780_selfdestruct/selfdestruct_revert/*` (2 files) — under-charge 112. Pattern: a beneficiary address gets touchAccount'd (not createObjectChange) by SELFDESTRUCT to an `pre.fund_eoa(amount=0)` recipient. Erigon's TouchAccount path doesn't emit `createObjectChange` for the address even though EELS treats it as a new account at frame end (existed_at_tx_entry=false in pre-state). Walk misses +112. -- `frontier/opcodes/*`, `frontier/scenarios/*` (4 files) — same +112 under-count pattern. +Crucial subtlety inside `chargeFrameStateGas` for the credit path: `executionStateGas` is decremented by the `accountRefund` portion of the credit (capped by `creditGas`), NOT by the full credit. The cleared-slot portion of the credit goes to the reservoir (state_gas_reservoir in EELS terms) but doesn't decrement the per-tx `executionStateGas` counter. EIP-6780 same-tx-CREATE+SELFDESTRUCT of an account that EXISTED at tx-entry needs `executionStateGas` to go negative to offset intrinsic_state — that path is preserved via the accountRefund decrement. -**Receipt hash mismatch** (not gas-related): -- `amsterdam/eip8037 sstore_restoration_charge_in_ancestor` (CALLCODE/DELEGATECALL receipt fields). -- `cancun/create/create_oog_from_eoa_refunds` — receipt hash mismatch on multiple subtests. -- `cancun/eip6780_selfdestruct/recursive_contract_creation_and_selfdestruct` — receipt hash on `recursion_depth_3` only. -- `cancun/eip6780_selfdestruct/recreate_self_destructed_contract_different_txs` — same pattern. +Together these fixed `cancun/create/create_oog_from_eoa_refunds` (6 sub-tests, all `sstore_callcode/sstore_delegatecall × no_oog/oog_*` variants), the `cancun/eip6780_selfdestruct/recursive_contract_creation_and_selfdestruct` and `recreate_self_destructed_contract_different_txs` receipt-hash failures, and `amsterdam/eip8037 sstore_restoration_charge_in_ancestor` (6 CALLCODE/DELEGATECALL variants). -**System contract / under-charge by larger amounts**: -- `prague/eip6110_deposits/deposit` — under-charge by 144 bytes (1 account + 1 slot) -- `prague/eip7002_el_triggerable_withdrawals/withdrawal_requests` — under-charge by 960 bytes (large) -- `prague/eip7251_consolidations/consolidation_requests` — similar pattern +**Fix #10: clamp `txnGasUsedB4Refunds` underflow when state-gas credits exceed consumption** (`TxnExecutor` Apply and Execute paths) -**Other edge cases**: -- `amsterdam/eip7708 selfdestruct_to_system_address` — under-charge to 0 execution_state. SELFDESTRUCT to 0xff…fe (system address) needs special handling. -- `byzantium/eip214_staticcall` — only `precompile_0x08-zero_value` variant; STATICCALL to BN256_PAIRING with value=0. -- `constantinople/eip1052 extcodehash_created_and_deleted` — CALL variant, over-charge 115 bytes (irregular). -- `tangerine_whistle/eip150_selfdestruct/*` (2 files) — exact_gas variants where minor accounting drift fails strict OOG limits. -- `shanghai/eip3651_warm_coinbase warm_coinbase_call_out_of_gas` — under-charge 9.9 bytes (irregular). +`txnGasUsedB4Refunds = initialGas.Total() - gasRemaining.Total()` underflowed when EIP-6780 same-tx-CREATE+SELFDESTRUCT credits (and now cleared-slot credits) made `gasRemaining.State` exceed `initialGas.State`. The receipt's `cumulativeGasUsed` ended up as `2^64 - small_number`. Clamp to 0 when remaining exceeds initial; the downstream `max(FloorGasCost, …)` ensures a valid receipt gas value. Applied at both ApplyMessage paths in `txn_executor.go`. -### Common root cause for the +112 under-charge family +**Fix #11: resetObjectChange charges +112 when `prev.original.Empty()`** -Erigon's `AddBalance(addr, amount)` path: -- For `amount.IsZero()` → `TouchAccount` → `GetOrNewStateObject` (which emits `createObjectChange`) AND a `touchAccount` journal entry. -- For non-zero amount → `GetOrNewStateObject` directly → emits `createObjectChange` if account didn't exist. +The walk's resetObjectChange handler previously skipped the +112 charge always, on the rationale that `resetObjectChange` corresponds to a previously-existing account. But `previous` may be a stateObject for an account that was effectively non-existent at tx-entry — e.g., a system address that an earlier system call's TouchAccount materialized as an empty stateObject, then EIP-161-pruned at the system call's FinalizeTx. From EELS's `compute_state_byte_diff` perspective such accounts have `existed_at_tx_entry = False` and qualify for +112 on creation. `prev.original.Empty()` is the correct signal: `original` is preserved across multiple resets and reflects the state at the very first creation, so an empty `original` indicates the account didn't exist when this stateObject's lineage began. -So in either path, `createObjectChange` SHOULD fire when the account first appears. The journal trace for the failing test shows `touchAccount` at the address but NO `createObjectChange`. This suggests the address was "resolved" by a prior read (e.g., access list addition) which loaded a stateObject, and the TouchAccount inside AddBalance found it pre-existing. +**Fix #12: getStateObject treats EIP-161-empty versionMap accounts as non-existent** -Possible fix paths (none implemented this session): -1. Track per-address state in walk: account in `account_writes` with `account_now != None && !existed_at_frame_entry && !existed_at_tx_entry` → +112 bytes. Requires journal-walk to recognize "this address became non-empty during this tx" via balanceChange/touchAccount, not just createObjectChange. -2. Mirror Erigon's `nilAccounts + balanceInc` mechanism in walk: if address was loaded as nil pre-tx but ended up with non-zero balance/code, count +112. -3. Generate fixtures from updated EELS spec (already on snobal-devnet-5; no further fixture updates expected). +In the parallel-executor path, `getStateObject` calls `getVersionedAccount` to read the address from the versionMap. If a system call earlier in the same block had touched the address (creating an empty stateObject that survives into the versionMap), `getVersionedAccount` returns a non-nil empty Account. Previously Erigon called `stateObjectForAccount(addr, account)` to materialise it as a stateObject WITHOUT firing any journal entry, then `GetOrNewStateObject` saw a non-deleted stateObject and skipped `createObject`. The walk never saw a `createObjectChange` for an address that EELS counts as freshly-created at the frame end, missing +112×CPSB. -Each remaining cluster needs per-test EELS trace comparison. Investigation tooling at `/tmp/execution-specs` (devnets/snobal/5 branch). +Fix: in `getStateObject`, when `getVersionedAccount` returns a non-nil but EIP-161-empty Account, cache it in `nilAccounts` and return nil. Subsequent accesses see the cached nil; `AddBalance(addr, non_zero)` flows through `GetOrNewStateObject → createObject(addr, nil)` → fires `createObjectChange`. Fixes `amsterdam/eip7708 selfdestruct_to_system_address` (the test's SELFDESTRUCT transfers value to 0xff…fe, which prior block-init system calls had emptied; with the fix Erigon properly counts +112 for the system address re-creation, matching EELS's expected `block_state_gas_used = 131,488`). ## Found test challenges diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index 4aa838ad526..fe0ffcffd97 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -408,9 +408,11 @@ func (st *TxnExecutor) ApplyFrame() (*evmtypes.ExecutionResult, error) { st.initialGas = st.gasRemaining.Plus(imdGas) // EIP-8037 × EIP-7702: intrinsic_state_gas is worst-case (assumes all auths // create new accounts). For each existing-account auth, 112 × cpsb is - // refunded to the reservoir so execution can draw from it; intrinsic_state_gas - // is immutable after validation, so the block-level state gas keeps the - // worst-case value. + // refunded to the reservoir so execution can draw from it. Per EIP-7778 + // the block-level state gas keeps the worst-case value (no refund + // subtraction); the receipt-level deduction surfaces naturally because + // txnGasUsedB4Refunds = initialGas.Total() - gasRemaining.Total() and + // gasRemaining.Total() includes the refunded portion. if stateIgasRefund > 0 && rules.IsAmsterdam { st.gasRemaining.State += stateIgasRefund } @@ -614,8 +616,28 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. } if rules.IsAmsterdam { st.blockRegularGasUsed = max(imdGas.Regular+st.evm.RegularGasConsumed(), intrinsicGasResult.FloorGasCost) - st.blockStateGasUsed = imdGas.State + st.evm.ExecutionStateGas() - st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + // EIP-7778: block_state_gas_used does NOT subtract refunds (including + // the EIP-7702 auth-list refund). intrinsic_state_gas is worst-case + // (assumes all auths create new accounts) and stays in the block-level + // total even when authority is non-empty; only the receipt-level + // txnGasUsedB4Refunds reflects the reservoir replenish (via lower + // gasRemaining usage). + // executionStateGas is signed (can be negative due to EIP-6780 refunds); + // clamp the sum to 0 since block_state_gas_used is a uint64 counter. + if execState := st.evm.ExecutionStateGas(); int64(imdGas.State)+execState > 0 { + st.blockStateGasUsed = uint64(int64(imdGas.State) + execState) + } else { + st.blockStateGasUsed = 0 + } + // txnGasUsedB4Refunds: Total can underflow if EIP-6780 same-tx + // selfdestruct credits on gas.State (via chargeFrameStateGas) make + // gasRemaining.Total() exceed initialGas.Total(). Clamp at 0; the + // FloorGasCost max below ensures a valid receipt gas value. + if st.gasRemaining.Total() > st.initialGas.Total() { + st.txnGasUsedB4Refunds = 0 + } else { + st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + } refund := min(st.txnGasUsedB4Refunds/refundQuotient, st.state.GetRefund().Regular) st.txnGasUsed = max(intrinsicGasResult.FloorGasCost, st.txnGasUsedB4Refunds-refund) } else if rules.IsPrague { @@ -632,11 +654,22 @@ func (st *TxnExecutor) Execute(refunds bool, gasBailout bool) (result *evmtypes. st.refundGas() } else if rules.IsAmsterdam { executionStateGas := st.evm.ExecutionStateGas() - blockState := imdGas.State + executionStateGas + // Clamp signed execution state gas to non-negative when summing with + // intrinsic for the block-level uint64 counter. + var blockState uint64 + if int64(imdGas.State)+executionStateGas > 0 { + blockState = uint64(int64(imdGas.State) + executionStateGas) + } blockRegular := imdGas.Regular + st.evm.RegularGasConsumed() st.blockRegularGasUsed = max(blockRegular, intrinsicGasResult.FloorGasCost) st.blockStateGasUsed = blockState - st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + // txnGasUsedB4Refunds: clamp underflow when state-gas credits cause + // gasRemaining to exceed initialGas (see refunds branch above). + if st.gasRemaining.Total() > st.initialGas.Total() { + st.txnGasUsedB4Refunds = 0 + } else { + st.txnGasUsedB4Refunds = st.initialGas.Total() - st.gasRemaining.Total() + } st.txnGasUsed = max(st.txnGasUsedB4Refunds, intrinsicGasResult.FloorGasCost) } else { // No-refund path: gasBailout (trace_call) or !refunds. diff --git a/execution/state/intra_block_state.go b/execution/state/intra_block_state.go index 189ff6566a2..453a0f5b5bc 100644 --- a/execution/state/intra_block_state.go +++ b/execution/state/intra_block_state.go @@ -1519,6 +1519,18 @@ func (sdb *IntraBlockState) getStateObject(addr accounts.Address, recordRead boo } if account != nil { + if account.Empty() { + // EIP-161 + EIP-8037: an empty account loaded from versionMap + // (e.g., system address touched by a system call earlier in the + // block, or an account whose only state is a TouchAccount) was + // pruned by EIP-161 at its source tx's FinalizeTx. Treat as + // non-existent so that subsequent writes (AddBalance, SSTORE) + // fire a createObjectChange via createObject. This matches EELS + // compute_state_byte_diff's existed_at_tx_entry semantics, which + // uses EIP-161 "exists" (account_now != None). + sdb.nilAccounts[addr] = struct{}{} + return nil, nil + } return sdb.stateObjectForAccount(addr, account), nil } @@ -1863,36 +1875,44 @@ func (sdb *IntraBlockState) JournalLength() int { } // ComputeFrameStateBytes walks the journal segment journal[start:end] and -// returns the total state bytes attributable to that frame, per EIP-8037. +// returns the total state bytes attributable to that frame, plus a refund +// for accounts created and selfdestructed in the same tx (EIP-6780). // // Rules: -// - Account creations (createObjectChange / resetObjectChange), first per -// address: skip if account == excludeCreate. Skip if stateObject is nil. -// If applyFilter AND .newlyCreated && .selfdestructed, skip. Otherwise -// +StateBytesNewAccount. -// - Code deposits (codeChange), first per address: same selfdestruct filter. +// - createObjectChange (account didn't exist pre-tx), first per address: +// skip if account == excludeCreate, skip if stateObject is nil or empty +// (EIP-161 prune). Otherwise +StateBytesNewAccount. +// - resetObjectChange (account existed pre-tx with balance only): never +// contributes +112 — the account record was already paid for at the +// prior tx that funded it. (EELS compute_state_byte_diff: the +112 +// condition requires not existed_at_tx_entry, which fails for any +// pre-existing account.) +// - Code deposits (codeChange), first per address: skip if not alive. // If prevhash was empty AND current stateObject.code is non-empty, // +len(code). -// - Storage 0→non-zero (storageChange), first per (address, key): same -// filter. Use the entry's originalValue (= tx-entry value) — when +// - Storage 0→non-zero (storageChange), first per (address, key): skip +// if not alive. Use the entry's originalValue (= tx-entry value) — when // originalValue.IsZero() AND the current value is non-zero, +32. // -// All other entry types (selfdestruct/balance/nonce/refund/log/access-list/ -// transient/touch/balanceIncrease/fakeStorage) are skipped. -// -// applyFilter is set true only at the top frame's commit (depth==0). Sub-frames -// pass false so that EIP-6780 net-zero cases temporarily over-count and the -// negative-delta credit at top frame compensates. +// At the top frame (applyFilter=true), accountRefund mirrors EELS's +// process_message_call refund for accounts in (created_accounts ∩ +// accounts_to_delete): for each address that's newlyCreated && selfdestructed, +// 112 + code_bytes + non_zero_storage_bytes. The chargeFrameStateGas caller +// subtracts this from the per-frame delta. This applies to both excludeCreate +// (top-level CREATE C) and nested addresses uniformly. // // excludeCreate is the contract address of a top-level CREATE tx (already -// covered by the 112×CPSB intrinsic). Pass NilAddress otherwise. +// covered by the 112×CPSB intrinsic). Pass NilAddress otherwise. Bytes for +// excludeCreate are not added to total, but ARE included in accountRefund +// when the address is newlyCreated && selfdestructed (so that the intrinsic- +// charged 112+code+storage gets refunded against the negative delta). func (sdb *IntraBlockState) ComputeFrameStateBytes( start, end int, applyFilter bool, excludeCreate accounts.Address, -) uint64 { +) (total int64, accountRefund uint64) { if start >= end { - return 0 + return 0, 0 } type slotKey struct { addr accounts.Address @@ -1902,43 +1922,103 @@ func (sdb *IntraBlockState) ComputeFrameStateBytes( seenCode := make(map[accounts.Address]struct{}) seenSlot := make(map[slotKey]struct{}) + // Per-address tracking for the top-frame refund. Only populated when + // applyFilter is true. The refund mirrors EELS's process_message_call + // `accounts_to_delete ∩ created_accounts` refund. + type acctTrack struct { + accountBytes uint64 // 112 if a create/reset entry was seen for this addr + codeBytes uint64 + storageBytes uint64 + } + var acctData map[accounts.Address]*acctTrack + if applyFilter { + acctData = make(map[accounts.Address]*acctTrack) + } + track := func(addr accounts.Address) *acctTrack { + if a, ok := acctData[addr]; ok { + return a + } + a := &acctTrack{} + acctData[addr] = a + return a + } + + // Pre-track excludeCreate at the top frame: its createObjectChange is + // emitted by evm.create() BEFORE frameStart is captured (the snapshot is + // pushed after CreateAccount), so the walk never sees it. The intrinsic + // charged 112×CPSB for this address; if it gets selfdestructed in the same + // tx, the refund needs to include 112 (matching EELS's accounts_to_delete + // refund). Code/storage are picked up normally by the walk if their + // journal entries fire after frameStart. + if applyFilter && excludeCreate != accounts.NilAddress { + track(excludeCreate).accountBytes = params.StateBytesNewAccount + } + // liveAccount returns the stateObject for addr and whether it should be - // counted, applying the EIP-6780 selfdestruct filter at the top frame. + // counted, applying the EIP-161 filter (empty accounts get pruned at tx + // finalize, so they shouldn't be charged for creation — e.g. + // AddBalance(X, 0) on a non-existent X creates a phantom empty + // stateObject). The filter applies at ALL frames, matching EELS's + // compute_state_byte_diff which uses `account_now != None` (EIP-161 + // "exists" semantics). The EIP-6780 filter (newlyCreated && + // selfdestructed) is NOT applied here — instead, those accounts are + // refunded explicitly via accountRefund (matching EELS's + // accounts_to_delete refund path). liveAccount := func(addr accounts.Address) (*stateObject, bool) { so := sdb.stateObjects[addr] if so == nil { return nil, false } - if applyFilter && so.newlyCreated && so.selfdestructed { + if so.data.Empty() { return so, false } return so, true } - var total uint64 for i := start; i < end && i < len(sdb.journal.entries); i++ { switch e := sdb.journal.entries[i].(type) { case createObjectChange: - if e.account == excludeCreate { - continue - } if _, ok := seenAccount[e.account]; ok { continue } seenAccount[e.account] = struct{}{} - if _, alive := liveAccount(e.account); alive { - total += params.StateBytesNewAccount - } - case resetObjectChange: - if e.account == excludeCreate { + _, alive := liveAccount(e.account) + if !alive { continue } + if e.account != excludeCreate { + total += int64(params.StateBytesNewAccount) + } + if applyFilter { + track(e.account).accountBytes = params.StateBytesNewAccount + } + case resetObjectChange: if _, ok := seenAccount[e.account]; ok { continue } seenAccount[e.account] = struct{}{} - if _, alive := liveAccount(e.account); alive { - total += params.StateBytesNewAccount + _, alive := liveAccount(e.account) + if !alive { + continue + } + // EIP-8037 +112 rule: charge for accounts that didn't exist at + // tx entry. resetObjectChange is fired by createObject when + // `previous != nil` — but `previous` may be a stateObject + // representing a DELETED account (e.g., system address pruned by + // EIP-161 in a prior tx, or any address whose only prior + // existence was an EIP-161-empty stateObject). Use + // `prev.original.Empty()` to detect when the address was + // effectively non-existent at tx start: original is the account + // state before any modifications, preserved across multiple + // resets in the same tx. If empty, the address did not exist at + // tx entry → charge +112 (matches EELS rule + // `account_now != None && !existed_at_frame_entry && + // !existed_at_tx_entry`). + if e.prev != nil && e.prev.original.Empty() && e.account != excludeCreate { + total += int64(params.StateBytesNewAccount) + } + if applyFilter { + track(e.account).accountBytes = params.StateBytesNewAccount } case codeChange: if _, ok := seenCode[e.account]; ok { @@ -1949,10 +2029,12 @@ func (sdb *IntraBlockState) ComputeFrameStateBytes( if !alive { continue } - // EIP-8037 code-deposit rule: counts only if prior code hash was - // empty and current code is non-empty. if e.prevhash.IsEmpty() && len(so.code) > 0 { - total += uint64(len(so.code)) + codeLen := uint64(len(so.code)) + total += int64(codeLen) + if applyFilter { + track(e.account).codeBytes = codeLen + } } case storageChange: k := slotKey{addr: e.account, key: e.key} @@ -1964,19 +2046,53 @@ func (sdb *IntraBlockState) ComputeFrameStateBytes( if !alive { continue } - // EIP-8037 new-slot rule: tx-entry zero AND current non-zero. if !e.originalValue.IsZero() { + // tx-entry value is non-zero: no rule applies (neither + // new-slot nor cleared-slot-with-tx-entry-zero). continue } + // EIP-8037 four-case rule (matching EELS compute_state_byte_diff): + // - New slot: current != 0 && frame_entry == 0 && tx_entry == 0 → +32 + // - Cleared slot, zero at tx start: current == 0 && frame_entry != 0 + // && tx_entry == 0 → -32 + // - Other transitions: 0 + // + // e.prevalue is the slot's value just before this storageChange. + // Because seenSlot dedups to the FIRST entry in the segment, + // e.prevalue is the slot's value at frame-entry (for sub-frames) + // or tx-entry (for top frame). current, _ := so.GetState(e.key) - if !current.IsZero() { + if !current.IsZero() && e.prevalue.IsZero() { total += 32 + if applyFilter { + track(e.account).storageBytes += 32 + } + } else if current.IsZero() && !e.prevalue.IsZero() { + // Cleared slot. Credit -32 to walkTotal (signed), so + // chargeFrameStateGas returns the gas to the reservoir. + // Mirrors EELS: a slot set in an ancestor frame and cleared + // in this frame nets out to 0 state bytes. + total -= 32 } default: // All other entry types are not state-bytes-relevant. } } - return total + + if applyFilter { + for addr, a := range acctData { + so := sdb.stateObjects[addr] + if so == nil { + continue + } + if !(so.newlyCreated && so.selfdestructed) { + continue + } + accountRefund += a.accountBytes + a.codeBytes + a.storageBytes + } + } + + return total, accountRefund } func (sdb *IntraBlockState) PopSnapshot(snapshot int) { diff --git a/execution/state/journal.go b/execution/state/journal.go index 5ca25826472..6440b3aae36 100644 --- a/execution/state/journal.go +++ b/execution/state/journal.go @@ -206,6 +206,17 @@ func (ch createObjectChange) revert(s *IntraBlockState) error { } delete(s.stateObjects, ch.account) delete(s.stateObjectsDirty, ch.account) + // Nullify the AddressPath versionedWrite that createObject emitted. Without + // this, a subsequent versionedRead in the same tx would see the stale + // write and re-materialize a stateObject for this address via + // stateObjectForAccount (no journal entry), causing EIP-8037 to miss the + // +112 byte charge when the address is then re-created in a later + // (non-reverted) frame. Keep the entry in the WriteSet (Val=nil) so that + // MakeWriteSet's parallel-mode cleanup can still see this address as + // reverted and clear stale entries from the global versionMap. + if s.versionMap != nil { + s.versionedWrites.UpdateVal(ch.account, AccountKey{Path: AddressPath}, (*accounts.Account)(nil)) + } return nil } diff --git a/execution/vm/errors.go b/execution/vm/errors.go index 164f201e384..5e7b56d117a 100644 --- a/execution/vm/errors.go +++ b/execution/vm/errors.go @@ -46,6 +46,13 @@ var ( ErrReturnStackExceeded = errors.New("return stack limit reached") ErrInvalidCode = errors.New("invalid code") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + // ErrFrameStateGasOOG is a soft OOG raised by EIP-8037 frame-end state-gas + // accounting when the combined state-gas reservoir + remaining regular gas + // cannot cover the per-frame state-gas charge. Per the EELS reference, this + // rolls back state changes for the frame but PRESERVES the frame's remaining + // gas (it is returned to the parent), unlike a true exceptional halt which + // burns it. Treated as a REVERT-equivalent in handleFrameRevert. + ErrFrameStateGasOOG = errors.New("frame state gas out of gas") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/execution/vm/evm.go b/execution/vm/evm.go index b14da39727f..18bdc77c79d 100644 --- a/execution/vm/evm.go +++ b/execution/vm/evm.go @@ -99,12 +99,17 @@ type EVM struct { // executionStateGas accumulates the EIP-8037 state-gas charges (and credits) // across all frame commits in this tx. Charged at frame-commit time via the // journal-walk-and-delta algorithm (see IntraBlockState.ComputeFrameStateBytes). - // Reset to 0 on top-level revert (depth==0 && err != nil). - executionStateGas uint64 + // Reset to 0 on top-level revert (depth==0 && err != nil). Signed because + // per-tx refunds (EIP-6780 same-tx selfdestruct, EIP-7702 auth-list refund) + // can drive this counter negative; the block-level computation clamps to 0 + // (block_state_gas_used = max(0, intrinsic_state + executionStateGas)). + executionStateGas int64 // committedChildBytes is a per-depth stack of running totals — when a child // frame commits, its walkTotal is added to the parent's slot. Used at // frame-commit to compute delta = walkTotal - committedChildBytes[depth]. - committedChildBytes []uint64 + // Signed because cleared-slot rules (EIP-8037) and child frames that net + // shrink state can produce negative walkTotals that propagate to parents. + committedChildBytes []int64 } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -167,7 +172,7 @@ func (evm *EVM) Cancelled() bool { return evm.abort.Load() } // ExecutionStateGas returns the EIP-8037 execution state gas accumulated across // all frame commits in the current tx. Reset to 0 on top-level revert. -func (evm *EVM) ExecutionStateGas() uint64 { return evm.executionStateGas } +func (evm *EVM) ExecutionStateGas() int64 { return evm.executionStateGas } // RegularGasConsumed returns the total regular gas charged during tx execution (for block-level accounting) func (evm *EVM) RegularGasConsumed() uint64 { return evm.regularGasConsumed } @@ -188,7 +193,7 @@ func (evm *EVM) pushFrameAccumulator() { // popFrameAccumulator pops the top of committedChildBytes and returns its value. // At commit time the caller passes the popped value to the parent via // propagateChildBytes. On revert the caller discards it. -func (evm *EVM) popFrameAccumulator() uint64 { +func (evm *EVM) popFrameAccumulator() int64 { n := len(evm.committedChildBytes) if n == 0 { return 0 @@ -200,20 +205,49 @@ func (evm *EVM) popFrameAccumulator() uint64 { // propagateChildBytes adds walkTotal to the parent's committedChildBytes slot // after a child frame commits. If we are at depth 0 (no parent) this is a no-op. -func (evm *EVM) propagateChildBytes(walkTotal uint64) { +// +// Negative walkTotals (from EIP-8037 cleared-slot credits) are NOT propagated: +// matching EELS, the reservoir credit happens at the child frame, while the +// parent's `already_paid` (committedChildBytes) tracks only positive charges. +// Reservoir gas itself is propagated via the gas return mechanism (the parent +// receives the child's leftover gas, which includes any credited state gas). +func (evm *EVM) propagateChildBytes(walkTotal int64) { n := len(evm.committedChildBytes) if n == 0 { return } - evm.committedChildBytes[n-1] += walkTotal + if walkTotal > 0 { + evm.committedChildBytes[n-1] += walkTotal + } +} + +// adjustGasStateAndExecution applies a signed `restoreGas` adjustment to the +// frame's state-gas reservoir (gas.State) and the running per-tx +// `executionStateGas` counter. Used on frame revert to undo the per-byte effects +// committed by descendants: positive `restoreGas` returns previously-charged +// gas to the reservoir (and decrements executionStateGas); negative +// `restoreGas` reverses a previously-credited descendant (e.g. EIP-8037 +// cleared-slot) and decrements the reservoir. +func adjustGasStateAndExecution(gas *mdgas.MdGas, executionStateGas *int64, restoreGas int64) { + if restoreGas >= 0 { + gas.State += uint64(restoreGas) + } else { + sub := uint64(-restoreGas) + if gas.State >= sub { + gas.State -= sub + } else { + gas.State = 0 + } + } + *executionStateGas -= restoreGas } // chargeFrameStateGas runs the EIP-8037 frame-end state-gas accounting for a // successful call/create commit, when Amsterdam is active and the EVM is not -// in RestoreState mode. It walks the journal segment [frameStart, frameEnd), computes -// delta = walkTotal - committedChildBytes[depth], and: +// in RestoreState mode. It walks the journal segment [frameStart, frameEnd), +// computes delta = walkTotal - committedChildBytes[depth] - accountRefund, and: // - delta > 0 → charge delta×CPSB from gas.State (with spill into gas.Regular). -// On insufficient gas, returns ErrOutOfGas (caller must revert). +// On insufficient gas, returns ErrFrameStateGasOOG (caller treats as REVERT). // - delta < 0 → credit |delta|×CPSB back to gas.State and decrement // evm.executionStateGas. // @@ -221,52 +255,73 @@ func (evm *EVM) propagateChildBytes(walkTotal uint64) { // accumulator. The caller is responsible for pushing/popping the per-depth // accumulator slot via pushFrameAccumulator/popFrameAccumulator. // +// At depth==0 (top frame), the walk computes accountRefund — bytes for accounts +// in (newlyCreated && selfdestructed), mirroring EELS's accounts_to_delete ∩ +// created_accounts refund. This naturally subsumes the per-tx +// CREATE+SELFDESTRUCT case (e.g. test_selfdestruct_in_create_tx_initcode) and +// the cross-frame CREATE-then-SELFDESTRUCT cases (cancun/eip6780_selfdestruct). +// // excludeCreate is the contract address for top-level CREATE txs (depth==0 -// inside evm.create). NilAddress in all other cases. +// inside evm.create) — its 112-byte account record is intrinsic-charged so +// the walk skips it. NilAddress in all other cases. func (evm *EVM) chargeFrameStateGas( gas *mdgas.MdGas, frameStart int, depth int, excludeCreate accounts.Address, -) (walkTotal uint64, err error) { +) (walkTotal int64, err error) { frameEnd := evm.intraBlockState.JournalLength() applyFilter := depth == 0 - walkTotal = evm.intraBlockState.ComputeFrameStateBytes(frameStart, frameEnd, applyFilter, excludeCreate) + var accountRefund uint64 + walkTotal, accountRefund = evm.intraBlockState.ComputeFrameStateBytes(frameStart, frameEnd, applyFilter, excludeCreate) n := len(evm.committedChildBytes) - var childTotal uint64 + var childTotal int64 if n > 0 { childTotal = evm.committedChildBytes[n-1] } - if walkTotal == childTotal { + cpsb := evm.Context.CostPerStateByte + delta := walkTotal - childTotal - int64(accountRefund) + if delta == 0 { return walkTotal, nil } - cpsb := evm.Context.CostPerStateByte - if walkTotal > childTotal { - // Positive delta — charge it to gas.State, spilling into gas.Regular if - // the reservoir is insufficient. Tracking goes via useMdGas which - // increments evm.executionStateGas. - stateGas := (walkTotal - childTotal) * cpsb + if delta > 0 { + stateGas := uint64(delta) * cpsb var ok bool *gas, ok = useMdGas(evm, *gas, stateGas, mdgas.StateGas, evm.config.Tracer, tracing.GasChangeFrameStateGas) if !ok { - return walkTotal, ErrOutOfGas + return walkTotal, ErrFrameStateGasOOG } return walkTotal, nil } - // Negative delta — credit |delta|×CPSB back to gas.State and shrink the - // tx-level executionStateGas accordingly. - creditGas := (childTotal - walkTotal) * cpsb + creditGas := uint64(-delta) * cpsb if evm.config.Tracer != nil && evm.config.Tracer.OnGasChange != nil { evm.config.Tracer.OnGasChange(gas.State, gas.State+creditGas, tracing.GasChangeFrameStateGas) } gas.State += creditGas - if creditGas > evm.executionStateGas { - evm.executionStateGas = 0 - } else { - evm.executionStateGas -= creditGas + // EIP-8037: when delta < 0 (cleared-slot credits, EIP-6780 same-tx + // selfdestruct refunds) we credit the reservoir but DO NOT decrement + // executionStateGas. This matches EELS's apply_frame_state_gas, where the + // negative this_call_cost path only updates state_gas_reservoir and leaves + // state_gas_used unchanged. The reservoir credit propagates to the parent + // via the gas return mechanism, while the per-tx executionStateGas counter + // only tracks positive charges (which determine block_state_gas_used). + // + // Exception: account-refund (EIP-6780 same-tx-CREATE+SELFDESTRUCT of an + // account that EXISTED at tx-entry) DOES need to drive executionStateGas + // negative to offset the intrinsic_state contribution. That refund is in + // `accountRefund`; the cleared-slot credit is the part beyond accountRefund. + if int64(accountRefund) > 0 { + // Decrement executionStateGas only by the accountRefund portion of + // the credit (capped by total credit). The remainder of the credit + // (cleared-slot rules) does not affect executionStateGas. + acctCreditGas := accountRefund * cpsb + if acctCreditGas > creditGas { + acctCreditGas = creditGas + } + evm.executionStateGas -= int64(acctCreditGas) } return walkTotal, nil } @@ -277,12 +332,18 @@ func (evm *EVM) chargeFrameStateGas( // EIP-8037 state-gas accounting is journal-walk-based at frame commit, so a // reverted/halted frame contributes nothing — there's nothing to roll back // here for state gas (the frame-end charge never fired). +// +// ErrFrameStateGasOOG is treated like REVERT: state is rolled back but the +// frame's remaining gas is preserved (returned to parent), matching EELS +// where apply_frame_state_gas's OOG sets evm.error directly without raising +// ExceptionalHalt. func (evm *EVM) handleFrameRevert(gas *mdgas.MdGas, err error, depth int, snapshot int) { // 1. Revert state changes. evm.intraBlockState.RevertToSnapshot(snapshot, err) - // 2. On exceptional halt (not REVERT), burn remaining regular gas. - if err != ErrExecutionReverted { + // 2. On exceptional halt (not REVERT, not soft OOG from frame-state-gas), + // burn remaining regular gas. + if err != ErrExecutionReverted && err != ErrFrameStateGasOOG { if evm.chainRules.IsAmsterdam { evm.regularGasConsumed += gas.Regular } @@ -501,14 +562,9 @@ func (evm *EVM) call(typ OpCode, caller accounts.Address, callerAddress accounts // results anyway). At depth==0 this also naturally zeroes // evm.executionStateGas because the top frame's accumulator holds // the sum of all charged bytes for the tx. - if !evm.config.RestoreState && evm.chainRules.IsAmsterdam && poppedChildBytes > 0 { - restoreGas := poppedChildBytes * evm.Context.CostPerStateByte - gas.State += restoreGas - if restoreGas > evm.executionStateGas { - evm.executionStateGas = 0 - } else { - evm.executionStateGas -= restoreGas - } + if !evm.config.RestoreState && evm.chainRules.IsAmsterdam && poppedChildBytes != 0 { + restoreGas := poppedChildBytes * int64(evm.Context.CostPerStateByte) + adjustGasStateAndExecution(&gas, &evm.executionStateGas, restoreGas) } } evm.handleFrameRevert(&gas, err, depth, snapshot) @@ -780,14 +836,9 @@ func (evm *EVM) create(caller accounts.Address, codeAndHash *codeAndHash, gasRem // EIP-8037: on child revert or exceptional halt, restore all state // gas consumed by committed descendants to this frame's reservoir. // See evm.call() for the full rationale. - if !evm.config.RestoreState && evm.chainRules.IsAmsterdam && poppedChildBytes > 0 { - restoreGas := poppedChildBytes * evm.Context.CostPerStateByte - gasRemaining.State += restoreGas - if restoreGas > evm.executionStateGas { - evm.executionStateGas = 0 - } else { - evm.executionStateGas -= restoreGas - } + if !evm.config.RestoreState && evm.chainRules.IsAmsterdam && poppedChildBytes != 0 { + restoreGas := poppedChildBytes * int64(evm.Context.CostPerStateByte) + adjustGasStateAndExecution(&gasRemaining, &evm.executionStateGas, restoreGas) } } evm.handleFrameRevert(&gasRemaining, err, depth, snapshot) diff --git a/execution/vm/interpreter.go b/execution/vm/interpreter.go index cf453078216..7996165e036 100644 --- a/execution/vm/interpreter.go +++ b/execution/vm/interpreter.go @@ -182,7 +182,7 @@ func useMdGas(evm *EVM, initial mdgas.MdGas, gas uint64, t mdgas.MdGasType, trac initial.State, ok = useGas(initial.State, gas, tracer, reason) if ok { if evm != nil { - evm.executionStateGas += originalGas + evm.executionStateGas += int64(originalGas) } return initial, true } @@ -198,7 +198,7 @@ func useMdGas(evm *EVM, initial mdgas.MdGas, gas uint64, t mdgas.MdGasType, trac initial.State = 0 initial.Regular, _ = useGas(initial.Regular, needFromRegular, tracer, reason) if evm != nil { - evm.executionStateGas += originalGas + evm.executionStateGas += int64(originalGas) } return initial, true case mdgas.RegularGas: