// Copyright 2026 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library 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. // // The go-ethereum library 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 the go-ethereum library. If not, see . // Transaction- and block-level tests for EIP-8037 (multidimensional state-gas // metering). They apply whole transactions and inspect the 2D block gas pool // (cumulativeRegular / cumulativeState) and the receipt/peak figures. package core import ( "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) var ( cfg8037 = balChainConfig() signer8037 = types.LatestSigner(cfg8037) rules8037 = cfg8037.Rules(big.NewInt(0), true, 0) senderKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") senderAddr = crypto.PubkeyToAddress(senderKey.PublicKey) // state-gas charges in units (CPSB applied). newAccountState = uint64(params.AccountCreationSize * params.CostPerStateByte) // 183,600 newSlotState = uint64(params.StorageCreationSize * params.CostPerStateByte) // 97,920 authBaseState = uint64(params.AuthorizationCreationSize * params.CostPerStateByte) // 35,190 authWorstState = newAccountState + authBaseState // 218,790 ) // mkState builds an in-memory StateDB from a genesis allocation. func mkState(alloc types.GenesisAlloc) *state.StateDB { sdb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) for addr, acc := range alloc { sdb.CreateAccount(addr) if acc.Balance != nil { sdb.AddBalance(addr, uint256.MustFromBig(acc.Balance), tracing.BalanceChangeUnspecified) } if acc.Nonce != 0 { sdb.SetNonce(addr, acc.Nonce, tracing.NonceChangeGenesis) } if len(acc.Code) != 0 { sdb.SetCode(addr, acc.Code, tracing.CodeChangeUnspecified) } for k, v := range acc.Storage { sdb.SetState(addr, k, v) } } sdb.Finalise(true) return sdb } // amsterdamCoreEVM builds an Amsterdam EVM over statedb with fees disabled. func amsterdamCoreEVM(sdb *state.StateDB) *vm.EVM { ctx := vm.BlockContext{ CanTransfer: CanTransfer, Transfer: Transfer, GetHash: func(uint64) common.Hash { return common.Hash{} }, BlockNumber: big.NewInt(0), Random: &common.Hash{}, Difficulty: big.NewInt(0), BaseFee: big.NewInt(0), BlobBaseFee: big.NewInt(0), GasLimit: 60_000_000, CostPerStateByte: params.CostPerStateByte, } return vm.NewEVM(ctx, sdb, cfg8037, vm.Config{NoBaseFee: true}) } // applyMsg applies one transaction with a fresh block gas pool and returns the // execution result, the gas pool (for the 2D split) and any consensus error. func applyMsg(t *testing.T, sdb *state.StateDB, tx *types.Transaction) (*ExecutionResult, *GasPool, error) { t.Helper() evm := amsterdamCoreEVM(sdb) msg, err := TransactionToMessage(tx, signer8037, evm.Context.BaseFee) if err != nil { t.Fatalf("to message: %v", err) } gp := NewGasPool(evm.Context.GasLimit) // Drive the stateTransition directly (as ApplyMessage does) so the test can // inspect the final tx-level GasBudget vector via st.gasRemaining. evm.SetTxContext(NewEVMTxContext(msg)) st := newStateTransition(evm, msg, gp) res, err := st.execute() if err == nil && res != nil { assertPoolSane(t, res, gp) limit := min(msg.GasLimit, params.MaxTxGas) assertBudgetSane(t, vm.NewGasBudget(limit, msg.GasLimit-limit), st.gasRemaining) } return res, gp, err } // assertBudgetSane validates the final tx-level GasBudget vector: // // regular: RegularGas + UsedRegularGas + Spilled == initial.RegularGas // state: StateGas + UsedStateGas == initial.StateGas + Spilled // scalar: Used(initial) == UsedRegularGas + UsedStateGas func assertBudgetSane(t *testing.T, initial, got vm.GasBudget) { t.Helper() if got.RegularGas+got.UsedRegularGas+got.Spilled != initial.RegularGas { t.Fatalf("regular not conserved: R=%d usedR=%d spilled=%d, want sum %d", got.RegularGas, got.UsedRegularGas, got.Spilled, initial.RegularGas) } if int64(got.StateGas)+got.UsedStateGas != int64(initial.StateGas)+int64(got.Spilled) { t.Fatalf("state not conserved: S=%d usedS=%d spilled=%d, want %d+spilled", got.StateGas, got.UsedStateGas, got.Spilled, initial.StateGas) } if int64(got.Used(initial)) != int64(got.UsedRegularGas)+got.UsedStateGas { t.Fatalf("scalar mismatch: used=%d, usedR=%d usedS=%d", got.Used(initial), got.UsedRegularGas, got.UsedStateGas) } } // assertPoolSane validates the whole 2D block-gas-pool vector after a single tx. // // receipt: cumulativeUsed == res.UsedGas <= res.MaxUsedGas // pre-refund: cumulativeRegular + cumulativeState <= res.MaxUsedGas (peak) // bottleneck: Used() == max(cumulativeRegular, cumulativeState) <= initial func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool) { t.Helper() if gp.cumulativeUsed != res.UsedGas { t.Fatalf("receipt scalar = %d, want UsedGas %d", gp.cumulativeUsed, res.UsedGas) } if res.UsedGas > res.MaxUsedGas { t.Fatalf("post-refund gas %d exceeds peak %d", res.UsedGas, res.MaxUsedGas) } if sum := gp.cumulativeRegular + gp.cumulativeState; sum > res.MaxUsedGas { t.Fatalf("regular+state %d exceeds peak %d", sum, res.MaxUsedGas) } if gp.Used() != max(gp.cumulativeRegular, gp.cumulativeState) { t.Fatalf("block used %d != max(%d,%d)", gp.Used(), gp.cumulativeRegular, gp.cumulativeState) } if gp.Used() > gp.initial { t.Fatalf("block used %d exceeds limit %d", gp.Used(), gp.initial) } } // senderAlloc funds the sender with the given extra accounts merged in. func senderAlloc(extra types.GenesisAlloc) types.GenesisAlloc { alloc := types.GenesisAlloc{senderAddr: {Balance: big.NewInt(1e18)}} for a, acc := range extra { alloc[a] = acc } return alloc } // callTx builds a signed dynamic-fee call to `to` with zero fees. func callTx(nonce uint64, to common.Address, value int64, gas uint64, data []byte) *types.Transaction { return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{ ChainID: cfg8037.ChainID, Nonce: nonce, To: &to, Value: big.NewInt(value), Gas: gas, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), Data: data, }) } // createTx builds a signed contract-creation transaction. func createTx(nonce, gas uint64, initCode []byte) *types.Transaction { return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{ ChainID: cfg8037.ChainID, Nonce: nonce, To: nil, Value: big.NewInt(0), Gas: gas, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), Data: initCode, }) } var ( deploy3 = []byte{0x60, 0x03, 0x60, 0x00, 0xf3} // init: return 3 bytes of code revertI = []byte{0x60, 0x00, 0x60, 0x00, 0xfd} // init: REVERT ) // ===================== Top-level create transaction ====================== // A creation tx's intrinsic gas pre-charges one account creation as state gas. func TestCreateTxIntrinsicChargesAccountUnconditionally(t *testing.T) { cost, err := IntrinsicGas(nil, nil, nil, true, rules8037, params.CostPerStateByte) if err != nil { t.Fatal(err) } if cost.StateGas != newAccountState { t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, newAccountState) } } // Creating onto a pre-existing (balance-only) address refills the account // portion; only the code deposit is charged as state gas. func TestCreateTxPreexistingDestRefill(t *testing.T) { derived := crypto.CreateAddress(senderAddr, 0) sdb := mkState(senderAlloc(types.GenesisAlloc{derived: {Balance: big.NewInt(1)}})) _, gp, err := applyMsg(t, sdb, createTx(0, 1_000_000, deploy3)) if err != nil { t.Fatal(err) } if want := uint64(3 * params.CostPerStateByte); gp.cumulativeState != want { t.Fatalf("state gas = %d, want %d", gp.cumulativeState, want) } } // A creation tx that reverts refills the account-creation charge. func TestCreateTxRevertRefill(t *testing.T) { sdb := mkState(senderAlloc(nil)) res, gp, err := applyMsg(t, sdb, createTx(0, 1_000_000, revertI)) if err != nil { t.Fatal(err) } if !res.Failed() { t.Fatal("expected failed creation") } if gp.cumulativeState != 0 { t.Fatalf("state gas = %d, want 0 (refilled)", gp.cumulativeState) } } // An address collision burns gas_left while refilling the account charge. func TestCreateTxCollisionConsumesGasLeft(t *testing.T) { const gas = 1_000_000 derived := crypto.CreateAddress(senderAddr, 0) sdb := mkState(senderAlloc(types.GenesisAlloc{derived: {Nonce: 1}})) res, gp, err := applyMsg(t, sdb, createTx(0, gas, deploy3)) if err != nil { t.Fatal(err) } if !res.Failed() { t.Fatal("expected collision failure") } if gp.cumulativeState != 0 { t.Fatalf("state gas = %d, want 0 (refilled)", gp.cumulativeState) } // All forwarded gas_left is burned; only the refilled account charge (which // had spilled into regular) returns to gas_left. So regular gas consumed is // exactly tx.gas - newAccountState, with no other refund. if want := uint64(gas) - newAccountState; gp.cumulativeRegular != want { t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want) } } // ======================== Transaction validation ========================= // The regular dimension must have room for min(tx.gas, MaxTxGas). func TestValidationRegularGasAvailable(t *testing.T) { gp := NewGasPool(30_000_000) gp.cumulativeRegular = 29_000_000 if gp.CheckGasAmsterdam(2_000_000, 0) == nil { t.Fatal("expected regular dimension full") } if err := gp.CheckGasAmsterdam(1_000_000, 0); err != nil { t.Fatalf("regular fits but rejected: %v", err) } } // The state dimension must have room for the whole tx.gas. func TestValidationStateGasAvailable(t *testing.T) { gp := NewGasPool(30_000_000) gp.cumulativeState = 29_000_000 if gp.CheckGasAmsterdam(0, 2_000_000) == nil { t.Fatal("expected state dimension full") } if err := gp.CheckGasAmsterdam(0, 1_000_000); err != nil { t.Fatalf("state fits but rejected: %v", err) } } // tx.gas may exceed MaxTxGas: regular is capped at MaxTxGas while the state // dimension reserves the full tx.gas (the excess lands in the reservoir). func TestValidationStateGasOverflowAllowed(t *testing.T) { gas := uint64(params.MaxTxGas) + 5_000_000 gp := NewGasPool(40_000_000) if err := gp.CheckGasAmsterdam(min(gas, params.MaxTxGas), gas); err != nil { t.Fatalf("overflow tx rejected at pool: %v", err) } // A real transfer with gas above MaxTxGas is accepted under Amsterdam. sdb := mkState(senderAlloc(nil)) to := common.HexToAddress("0xc0ffee") if _, _, err := applyMsg(t, sdb, callTx(0, to, 1, gas, nil)); err != nil { t.Fatalf("tx with gas > MaxTxGas rejected: %v", err) } } // Intrinsic regular gas above MaxTxGas (EIP-7825 cap) is rejected. func TestValidationIntrinsicRegularCap(t *testing.T) { al := make(types.AccessList, 8000) // ~19.2M regular, over the 16.77M cap for i := range al { al[i].Address = common.BigToAddress(big.NewInt(int64(i + 1))) } tx := types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{ ChainID: cfg8037.ChainID, Nonce: 0, To: &senderAddr, Value: big.NewInt(0), Gas: 25_000_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), AccessList: al, }) if _, _, err := applyMsg(t, mkState(senderAlloc(nil)), tx); err == nil { t.Fatal("expected rejection for intrinsic regular over MaxTxGas") } } // ========================= Refund and gas used =========================== // clearSlots deploys a contract that zeroes slots 1..n, each preset to 1. func clearSlots(addr common.Address, n int) (types.GenesisAlloc, []byte) { var code []byte storage := make(map[common.Hash]common.Hash, n) for s := 1; s <= n; s++ { code = append(code, 0x60, 0x00, 0x60, byte(s), 0x55) // PUSH1 0; PUSH1 s; SSTORE storage[common.BytesToHash([]byte{byte(s)})] = common.BytesToHash([]byte{1}) } return types.GenesisAlloc{addr: {Code: append(code, 0x00), Storage: storage}}, nil } // tx_gas_used_before_refund (peak) exceeds the post-refund gas used. func TestGasUsedBeforeRefund(t *testing.T) { c := common.HexToAddress("0xc1ea0") alloc, _ := clearSlots(c, 1) res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, nil)) if err != nil { t.Fatal(err) } if res.MaxUsedGas <= res.UsedGas { t.Fatalf("peak %d must exceed post-refund %d", res.MaxUsedGas, res.UsedGas) } } // The refund is capped at 20% of gas used before refund. func TestRefundCappedAt20Percent(t *testing.T) { c := common.HexToAddress("0xc1ea3") alloc, _ := clearSlots(c, 3) // refund (3x4800) exceeds the 20% cap res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, nil)) if err != nil { t.Fatal(err) } if want := res.MaxUsedGas - res.MaxUsedGas/5; res.UsedGas != want { t.Fatalf("gas used = %d, want capped %d", res.UsedGas, want) } } // The EIP-7623 calldata floor is applied after the refund. func TestRefundCalldataFloorAfterRefund(t *testing.T) { data := make([]byte, 1000) // all-zero calldata: floor dominates a bare call floor, _ := FloorDataGas(rules8037, data, nil) to := common.HexToAddress("0xeeee") res, _, err := applyMsg(t, mkState(senderAlloc(nil)), callTx(0, to, 0, 1_000_000, data)) if err != nil { t.Fatal(err) } if res.UsedGas != floor { t.Fatalf("gas used = %d, want floor %d", res.UsedGas, floor) } } // When the floor exceeds the post-refund gas, it negates part of the refund. func TestRefundFloorNegatesRefund(t *testing.T) { c := common.HexToAddress("0xc1ea1") alloc, _ := clearSlots(c, 1) data := make([]byte, 1000) floor, _ := FloorDataGas(rules8037, data, nil) res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, data)) if err != nil { t.Fatal(err) } if res.UsedGas != floor { t.Fatalf("gas used = %d, want floor %d (refund negated)", res.UsedGas, floor) } } // ========================= Block-level accounting ======================== // The pool tracks regular and state cumulatively in separate counters. func TestBlockTracksTwoCounters(t *testing.T) { gp := NewGasPool(60_000_000) if err := gp.ChargeGasAmsterdam(100, 200, 300); err != nil { t.Fatal(err) } if gp.cumulativeRegular != 100 || gp.cumulativeState != 200 { t.Fatalf("counters = (%d,%d), want (100,200)", gp.cumulativeRegular, gp.cumulativeState) } } // Block gas used is the max of the two dimensions. func TestBlockGasUsedIsMax(t *testing.T) { gp := NewGasPool(60_000_000) gp.ChargeGasAmsterdam(100, 200, 300) if gp.Used() != 200 { t.Fatalf("block used = %d, want 200", gp.Used()) } } // Block validity is checked against the max dimension, not the sum. func TestBlockValidityAgainstMax(t *testing.T) { gp := NewGasPool(150) // regular 100 + state 120: sum 220 > 150 but max 120 <= 150 is valid. if err := gp.ChargeGasAmsterdam(100, 120, 0); err != nil { t.Fatalf("max within limit but rejected: %v", err) } // state 200 alone exceeds the limit. if err := gp.ChargeGasAmsterdam(0, 200, 0); err == nil { t.Fatal("expected block overflow on state dimension") } } // The block header gas_used reflects the bottleneck dimension (here, state), // which the base-fee update then equilibrates against. func TestBlockBaseFeeUsesMax(t *testing.T) { c := common.HexToAddress("0x5707e5") var code []byte for s := 1; s <= 5; s++ { code = append(code, 0x60, byte(s), 0x60, byte(s), 0x55) // SSTORE new slot s } env := newBALTestEnv(types.GenesisAlloc{c: {Code: append(code, 0x00)}}) engine := beacon.New(ethash.NewFaker()) _, blocks, _ := GenerateChainWithGenesis(env.gspec, engine, 1, func(_ int, b *BlockGen) { b.AddTx(env.tx(0, &c, big.NewInt(0), 1_000_000, 0, nil)) }) if want := uint64(5 * newSlotState); blocks[0].GasUsed() != want { t.Fatalf("block gas used = %d, want %d (state bottleneck)", blocks[0].GasUsed(), want) } } // Receipt cumulative_gas_used is the running sum of per-tx gas (post-refund, // post-floor), so consecutive receipts differ by exactly that tx's gas. func TestReceiptCumulativeGasUsed(t *testing.T) { env := newBALTestEnv(nil) a, b := common.HexToAddress("0xaaaa"), common.HexToAddress("0xbbbb") engine := beacon.New(ethash.NewFaker()) _, _, receipts := GenerateChainWithGenesis(env.gspec, engine, 1, func(_ int, g *BlockGen) { g.AddTx(env.tx(0, &a, big.NewInt(1), txGasNewAccount, 0, nil)) g.AddTx(env.tx(1, &b, big.NewInt(1), txGasNewAccount, 0, nil)) }) r := receipts[0] if got := r[1].CumulativeGasUsed - r[0].CumulativeGasUsed; got != r[1].GasUsed { t.Fatalf("cumulative delta = %d, want tx gas %d", got, r[1].GasUsed) } } // ======================= EIP-7702 authorizations ========================= // signAuth signs an authorization from authKey for the given delegate and nonce. func signAuth(t *testing.T, authKey string, delegate common.Address, nonce uint64) (types.SetCodeAuthorization, common.Address) { t.Helper() k, _ := crypto.HexToECDSA(authKey) auth, err := types.SignSetCode(k, types.SetCodeAuthorization{ ChainID: *uint256.MustFromBig(cfg8037.ChainID), Address: delegate, Nonce: nonce, }) if err != nil { t.Fatalf("sign auth: %v", err) } return auth, crypto.PubkeyToAddress(k.PublicKey) } func setCodeTx(nonce uint64, to common.Address, auths []types.SetCodeAuthorization) *types.Transaction { return types.MustSignNewTx(senderKey, signer8037, &types.SetCodeTx{ ChainID: uint256.MustFromBig(cfg8037.ChainID), Nonce: nonce, To: to, Value: new(uint256.Int), Gas: 1_000_000, GasFeeCap: new(uint256.Int), GasTipCap: new(uint256.Int), AuthList: auths, }) } const authKeyA = "0202020202020202020202020202020202020202020202020202002020202020" var delegate8037 = common.HexToAddress("0xde1e8a7e") // Intrinsic gas pre-charges the worst-case (account + indicator) per auth. func TestAuthIntrinsicWorstCase(t *testing.T) { cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, false, rules8037, params.CostPerStateByte) if err != nil { t.Fatal(err) } if cost.StateGas != authWorstState { t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, authWorstState) } } // An invalid authorization refills its entire intrinsic state-gas charge. func TestAuthInvalidRefillFull(t *testing.T) { k, _ := crypto.HexToECDSA(authKeyA) bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{ ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id }) sdb := mkState(senderAlloc(nil)) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{bad})) if err != nil { t.Fatal(err) } if gp.cumulativeState != 0 { t.Fatalf("state gas = %d, want 0 (fully refilled)", gp.cumulativeState) } } // A pre-existing authority refills the account portion (indicator stands). func TestAuthAccountExistsRefill(t *testing.T) { auth, authority := signAuth(t, authKeyA, delegate8037, 0) sdb := mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{auth})) if err != nil { t.Fatal(err) } if gp.cumulativeState != authBaseState { t.Fatalf("state gas = %d, want %d (account refilled)", gp.cumulativeState, authBaseState) } } // Setting a delegation on an already-delegated authority refills the indicator // portion (and the account portion, since the authority already exists). func TestAuthSetOnDelegatedRefillBase(t *testing.T) { auth, authority := signAuth(t, authKeyA, delegate8037, 0) pre := types.AddressToDelegation(common.HexToAddress("0xabcd")) sdb := mkState(senderAlloc(types.GenesisAlloc{authority: {Code: pre}})) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{auth})) if err != nil { t.Fatal(err) } if gp.cumulativeState != 0 { t.Fatalf("state gas = %d, want 0 (account+indicator refilled)", gp.cumulativeState) } } // A net-new delegation on a fresh authority keeps the full worst-case charge. func TestAuthSetNetNewNoRefill(t *testing.T) { auth, _ := signAuth(t, authKeyA, delegate8037, 0) sdb := mkState(senderAlloc(nil)) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{auth})) if err != nil { t.Fatal(err) } if gp.cumulativeState != authWorstState { t.Fatalf("state gas = %d, want %d (no refill)", gp.cumulativeState, authWorstState) } } // Clearing a delegation writes no indicator, so the indicator portion refills. func TestAuthClearRefillBase(t *testing.T) { auth, _ := signAuth(t, authKeyA, common.Address{}, 0) // clear (address ZERO) sdb := mkState(senderAlloc(nil)) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{auth})) if err != nil { t.Fatal(err) } if want := uint64(newAccountState); gp.cumulativeState != want { t.Fatalf("state gas = %d, want %d (indicator refilled)", gp.cumulativeState, want) } } // 0->a->0 in one tx: the indicator created by an earlier auth and cleared by a // later one writes zero net bytes, so both indicator charges refill. func TestAuthClearSameTxDoubleRefill(t *testing.T) { set, authority := signAuth(t, authKeyA, delegate8037, 0) clr, _ := signAuth(t, authKeyA, common.Address{}, 1) sdb := mkState(senderAlloc(nil)) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{set, clr})) if err != nil { t.Fatal(err) } _ = authority if want := uint64(newAccountState); gp.cumulativeState != want { t.Fatalf("state gas = %d, want %d (net-zero delegation)", gp.cumulativeState, want) } } // The same authority across two auths is charged for its account only once. func TestAuthDuplicateAuthorityOnce(t *testing.T) { a0, _ := signAuth(t, authKeyA, delegate8037, 0) a1, _ := signAuth(t, authKeyA, delegate8037, 1) sdb := mkState(senderAlloc(nil)) _, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{a0, a1})) if err != nil { t.Fatal(err) } if gp.cumulativeState != authWorstState { t.Fatalf("state gas = %d, want %d (leaf+indicator once)", gp.cumulativeState, authWorstState) } } // ===================== System contracts / system calls =================== // System call gas limit keeps 30M regular plus a state reservoir for new slots. func TestSystemCallGasLimit(t *testing.T) { limit, budget := systemCallGasBudget(amsterdamCoreEVM(mkState(nil))) if limit != 30_000_000 || budget.RegularGas != 30_000_000 { t.Fatalf("limit/regular = %d/%d, want 30M/30M", limit, budget.RegularGas) } } // The extra system budget is placed in the state reservoir (16 new slots). func TestSystemCallExtraInReservoir(t *testing.T) { _, budget := systemCallGasBudget(amsterdamCoreEVM(mkState(nil))) want := uint64(params.SystemMaxSStoresPerCall * params.CostPerStateByte * params.StorageCreationSize) if budget.StateGas != want { t.Fatalf("reservoir = %d, want %d", budget.StateGas, want) } } // System calls do not contribute to either block dimension: an empty block // (whose system calls still write state) reports zero gas used. func TestSystemCallNotCountedInBlock(t *testing.T) { env := newBALTestEnv(nil) engine := beacon.New(ethash.NewFaker()) _, blocks, _ := GenerateChainWithGenesis(env.gspec, engine, 1, func(_ int, b *BlockGen) {}) if blocks[0].GasUsed() != 0 { t.Fatalf("block gas used = %d, want 0 (system calls excluded)", blocks[0].GasUsed()) } }