core: implement EIP-8038 (#35216)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

Implements https://eips.ethereum.org/EIPS/eip-8038
This commit is contained in:
rjl493456442 2026-07-02 09:05:51 +08:00 committed by GitHub
parent 59e89e81e5
commit dd8dd1520f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 573 additions and 99 deletions

110
core/eip8038_test.go Normal file
View file

@ -0,0 +1,110 @@
// 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 <http://www.gnu.org/licenses/>.
// EIP-8038 authorization accounting tests. The per-authorization intrinsic gas
// pre-charges ACCOUNT_WRITE (regular) on top of REGULAR_PER_AUTH_BASE_COST.
// applyAuthorization refunds that ACCOUNT_WRITE to the refund counter in exactly
// the cases where no new account leaf is written: an invalid authorization, or
// an authority whose account already exists. These white-box tests invoke
// applyAuthorization directly and read the raw refund counter, so they observe
// the refund before the EIP-3529 cap is applied.
package core
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"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"
)
// newAuthTestTransition builds a minimal stateTransition with a state reservoir,
// suitable for calling applyAuthorization directly.
func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
st := newStateTransition(amsterdamCoreEVM(sdb), &Message{}, NewGasPool(30_000_000))
st.gasRemaining = vm.NewGasBudget(0, 1_000_000) // reservoir for state-gas refills
return st
}
// A net-new delegation on a fresh authority writes a new account leaf, so the
// intrinsic ACCOUNT_WRITE stands (no refund).
func TestAuthAccountWriteNetNewNoRefund(t *testing.T) {
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
st := newAuthTestTransition(mkState(senderAlloc(nil)))
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
t.Fatal(err)
}
if got := st.state.GetRefund(); got != 0 {
t.Fatalf("refund = %d, want 0 (net-new account write)", got)
}
}
// A pre-existing authority writes no new account leaf, so the intrinsic
// ACCOUNT_WRITE is refunded.
func TestAuthAccountWriteExistsRefund(t *testing.T) {
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
t.Fatal(err)
}
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
t.Fatalf("refund = %d, want %d (account already exists)", got, params.AccountWriteAmsterdam)
}
}
// An invalid authorization is skipped without writing any account leaf, so its
// intrinsic ACCOUNT_WRITE is refunded.
func TestAuthAccountWriteInvalidRefund(t *testing.T) {
k, _ := crypto.HexToECDSA(authKeyA)
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
})
st := newAuthTestTransition(mkState(senderAlloc(nil)))
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil {
t.Fatal("expected invalid-authorization error")
}
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
t.Fatalf("refund = %d, want %d (invalid authorization)", got, params.AccountWriteAmsterdam)
}
}
// The same authority across two authorizations writes its account leaf only
// once: the first auth pays ACCOUNT_WRITE, the second (which now sees the
// account as existing) is refunded.
func TestAuthAccountWriteDuplicateOnce(t *testing.T) {
a0, _ := signAuth(t, authKeyA, delegate8037, 0)
a1, _ := signAuth(t, authKeyA, delegate8037, 1)
st := newAuthTestTransition(mkState(senderAlloc(nil)))
delegates := map[common.Address]bool{}
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
t.Fatal(err)
}
if got := st.state.GetRefund(); got != 0 {
t.Fatalf("refund after first auth = %d, want 0", got)
}
if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil {
t.Fatal(err)
}
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
t.Fatalf("refund after duplicate auth = %d, want %d", got, params.AccountWriteAmsterdam)
}
}

View file

@ -87,7 +87,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
// Add gas for authorizations
if authList != nil {
if rules.IsAmsterdam {
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
gas.RegularGas += uint64(len(authList)) * (params.AccountWriteAmsterdam + params.RegularPerAuthBaseCost)
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
} else {
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
@ -126,14 +126,22 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
if accessList != nil {
addresses := uint64(len(accessList))
storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
// Amsterdam re-prices the per-entry access-list cost
addressCost := params.TxAccessListAddressGas
storageKeyCost := params.TxAccessListStorageKeyGas
if rules.IsAmsterdam {
addressCost = params.TxAccessListAddressGasAmsterdam
storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam
}
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas.RegularGas += addresses * params.TxAccessListAddressGas
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
gas.RegularGas += addresses * addressCost
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow
}
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
gas.RegularGas += storageKeys * storageKeyCost
// EIP-7981: access list data is charged in addition to the base charge.
if rules.IsAmsterdam {
@ -955,20 +963,13 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
}
// applyAuthorization applies an EIP-7702 code delegation to the state and,
// under EIP-8037, reconciles the per-authorization intrinsic state-gas pre-
// charge so that, per authority:
//
// - the account portion (AccountCreationSize × CPSB) is charged at most
// once, and only when the account did not exist before the tx
//
// - the delegation-indicator portion (AuthorizationCreationSize × CPSB) is
// charged at most once, and only when the authority ends the tx delegated
// having started it undelegated.
// adjust the pre-charged intrinsic cost accordingly.
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
authority, err := st.validateAuthorization(auth)
if err != nil {
if rules.IsAmsterdam {
st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
st.state.AddRefund(params.AccountWriteAmsterdam)
}
return err
}
@ -981,6 +982,7 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
} else {
if st.state.Exist(authority) {
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
st.state.AddRefund(params.AccountWriteAmsterdam)
}
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte

View file

@ -243,7 +243,7 @@ func TestIntrinsicGas(t *testing.T) {
// EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess
// (15,000). Plus base access-list charge + EIP-7981 extra.
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
},
{
@ -298,7 +298,7 @@ func TestIntrinsicGas(t *testing.T) {
want: vm.GasCosts{
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
@ -314,15 +314,16 @@ func TestIntrinsicGas(t *testing.T) {
},
isEIP2028: true,
isAmsterdam: true,
// EIP-8037 splits the auth-tuple charge into regular + state gas:
// regular: TxAuthTupleRegularGas (7500) per auth
// EIP-8037 splits the auth-tuple charge into regular + state gas, with
// the values finalized by EIP-8038:
// regular: ACCOUNT_WRITE (8,000) + REGULAR_PER_AUTH_BASE_COST (7,500) per auth
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
want: vm.GasCosts{
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
100*params.TxDataNonZeroGasEIP2028 +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
1*params.TxAuthTupleRegularGas,
1*(params.AccountWriteAmsterdam+params.RegularPerAuthBaseCost),
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
},
},

View file

@ -190,16 +190,25 @@ func TestSStoreChargedAtOpcodeEnd(t *testing.T) {
}
// The SSTORE reentrancy sentry checks gas_left only; the reservoir is excluded.
// Uses a noop write (1->1->1) so the sentry is the sole gate.
// Uses a noop write (1->1->1): the two PUSH1s cost 6, leaving gas_left at the
// sentry (2300) for a 2306 budget. Under EIP-8038 the cold-slot access that
// follows a cleared sentry costs COLD_STORAGE_ACCESS (3000).
func TestSStoreStipendExcludesReservoir(t *testing.T) {
// regular at the sentry, huge reservoir: must still fail.
// regular at the sentry, huge reservoir: must still fail, proving the
// reservoir does not count toward the sentry.
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(2306, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err == nil {
t.Fatal("expected sentry failure with regular gas at the limit")
}
// one more regular gas clears the sentry.
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(2307, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err != nil {
// Enough regular gas to clear the sentry and pay the cold-slot access
// (6 for the PUSH1s + COLD_STORAGE_ACCESS) succeeds with a huge reservoir.
regular := 6 + params.ColdStorageAccessAmsterdam
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(regular, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err != nil {
t.Fatalf("unexpected failure above sentry: %v", err)
}
// One gas short of the cold-slot access still fails (now on OOG, not sentry).
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(regular-1, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err == nil {
t.Fatal("expected OOG when regular gas cannot cover cold-slot access")
}
}
// ---- CALL / CREATE bytecode helpers ----

237
core/vm/eip8038_test.go Normal file
View file

@ -0,0 +1,237 @@
// 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 <http://www.gnu.org/licenses/>.
// Opcode-level tests for EIP-8038 (state-access gas cost update). They reuse the
// Amsterdam harness from eip8037_test.go and assert the re-priced regular-gas,
// state-gas and refund-counter accounting.
package vm
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"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/params"
"github.com/holiman/uint256"
)
// run8038 executes code at a contract address under the Amsterdam ruleset and
// returns the resulting budget together with the transaction's refund counter.
func run8038(t *testing.T, code []byte, gas GasBudget, value *uint256.Int, setup func(*state.StateDB, common.Address)) (GasBudget, uint64, error) {
t.Helper()
self := common.BytesToAddress([]byte("self"))
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.CreateAccount(self)
statedb.SetCode(self, code, tracing.CodeChangeUnspecified)
if setup != nil {
setup(statedb, self)
}
statedb.Finalise(true)
_, result, err := amsterdam8037EVM(statedb).Call(common.Address{}, self, nil, gas, value)
return result, statedb.GetRefund(), err
}
// TestEIP8038SStore exercises SSTORE under Amsterdam (EIP-8037 + EIP-8038),
// asserting the two-dimensional charge (regular + state gas) and the net refund
// counter. It covers single stores in isolation (the EIP-8038 cases-table rows,
// cold access), the warm-access variants, the dirty-slot refund reversals and
// multi-store round trips.
//
// Each sstore() is "PUSH1 val; PUSH1 slot; SSTORE", so the non-SSTORE overhead is
// 6 gas (two PUSH1) per store. The first store to a slot is cold and the rest are
// warm, so the access component is COLD_STORAGE_ACCESS + (n-1) * WARM_ACCESS for n
// stores. STORAGE_WRITE is charged once per "first change" (current == original).
// GAS_STORAGE_SET is state gas, charged when a slot is created from zero and
// refilled to the reservoir when that creation is undone within the same tx.
func TestEIP8038SStore(t *testing.T) {
const (
push = uint64(6) // two PUSH1 per SSTORE
cold = params.ColdStorageAccessAmsterdam
warm = params.WarmStorageReadCostEIP2929
write = params.StorageWriteAmsterdam
clear = params.StorageClearRefundAmsterdam
)
set := uint64(params.StorageCreationSize * params.CostPerStateByte) // GAS_STORAGE_SET
// access(n) is the access-only regular cost for n stores: cold first, warm rest.
access := func(n uint64) uint64 { return cold + (n-1)*warm }
cases := []struct {
name string
orig byte // committed (pre-tx) value; 0 means a fresh slot
vals []byte // values written to slot 0, in order
wantReg uint64
wantState int64
wantRfnd uint64
}{
// Single store, cold access (EIP-8038 cases table, Cold rows + noop).
{"noop (1->1)", 1, []byte{1}, push + cold, 0, 0},
{"create (0->1)", 0, []byte{1}, push + cold + write, int64(set), 0},
{"first change (1->2)", 1, []byte{2}, push + cold + write, 0, 0},
{"clear (1->0)", 1, []byte{0}, push + cold + write, 0, clear},
// Two stores, warm access on the second (Warm rows of the cases table).
{"create warm (0->0->1)", 0, []byte{0, 1}, 2*push + access(2) + write, int64(set), 0},
{"first change warm (1->1->2)", 1, []byte{1, 2}, 2*push + access(2) + write, 0, 0},
{"clear warm (1->1->0)", 1, []byte{1, 0}, 2*push + access(2) + write, 0, clear},
{"dirty modified again (1->2->3)", 1, []byte{2, 3}, 2*push + access(2) + write, 0, 0},
// Two stores, refund reversals when a slot returns toward its original.
{"reset to zero (0->1->0)", 0, []byte{1, 0}, 2*push + access(2) + write, 0, write},
{"reset to original (1->2->1)", 1, []byte{2, 1}, 2*push + access(2) + write, 0, write},
{"cleared then restored (1->0->1)", 1, []byte{0, 1}, 2*push + access(2) + write, 0, write},
{"cleared then new value (1->0->2)", 1, []byte{0, 2}, 2*push + access(2) + write, 0, 0},
// Three stores, round trips (note the state-gas refill on the 0-> path).
{"0->1->0->1", 0, []byte{1, 0, 1}, 3*push + access(3) + 2*write, int64(set), write},
{"1->0->1->0", 1, []byte{0, 1, 0}, 3*push + access(3) + 2*write, 0, clear + write},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var code []byte
for _, v := range tc.vals {
code = append(code, sstore(0, v)...)
}
var setup func(*state.StateDB, common.Address)
if tc.orig != 0 {
setup = setSlot(0, tc.orig)
}
res, refund, err := run8038(t, code, hugeBudget(), new(uint256.Int), setup)
if err != nil {
t.Fatal(err)
}
if res.UsedRegularGas != tc.wantReg {
t.Errorf("regular gas = %d, want %d", res.UsedRegularGas, tc.wantReg)
}
if res.UsedStateGas != tc.wantState {
t.Errorf("state gas = %d, want %d", res.UsedStateGas, tc.wantState)
}
if refund != tc.wantRfnd {
t.Errorf("refund = %d, want %d", refund, tc.wantRfnd)
}
})
}
}
// TestEIP8038SLoad checks the re-priced SLOAD access costs (cold 3000, warm 100).
func TestEIP8038SLoad(t *testing.T) {
push := uint64(3) // PUSH1 slot
// PUSH1 0x00; SLOAD
cold := []byte{0x60, 0x00, 0x54}
res, _, err := run8038(t, cold, hugeBudget(), new(uint256.Int), nil)
if err != nil {
t.Fatal(err)
}
if want := push + params.ColdStorageAccessAmsterdam; res.UsedRegularGas != want {
t.Fatalf("cold SLOAD = %d, want %d", res.UsedRegularGas, want)
}
// PUSH1 0x00; SLOAD; PUSH1 0x00; SLOAD -> second access is warm.
warm := []byte{0x60, 0x00, 0x54, 0x60, 0x00, 0x54}
res, _, err = run8038(t, warm, hugeBudget(), new(uint256.Int), nil)
if err != nil {
t.Fatal(err)
}
want := 2*push + params.ColdStorageAccessAmsterdam + params.WarmStorageReadCostEIP2929
if res.UsedRegularGas != want {
t.Fatalf("cold+warm SLOAD = %d, want %d", res.UsedRegularGas, want)
}
}
// TestEIP8038AccountAccess checks the re-priced cold-account access for the
// account-reading opcodes and the extra WARM_ACCESS surcharge for EXTCODESIZE
// and EXTCODECOPY (their second database read).
func TestEIP8038AccountAccess(t *testing.T) {
push20 := uint64(3)
addr := common.BytesToAddress([]byte("some-cold-account"))
// pushAddr emits PUSH20 <addr>.
pushAddr := func() []byte { return append([]byte{0x73}, addr.Bytes()...) }
cold := params.ColdAccountAccessAmsterdam
warm := params.WarmStorageReadCostEIP2929
t.Run("BALANCE", func(t *testing.T) {
code := append(pushAddr(), 0x31) // BALANCE
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
if err != nil {
t.Fatal(err)
}
if want := push20 + cold; res.UsedRegularGas != want {
t.Fatalf("cold BALANCE = %d, want %d", res.UsedRegularGas, want)
}
})
t.Run("EXTCODEHASH", func(t *testing.T) {
code := append(pushAddr(), 0x3f) // EXTCODEHASH
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
if err != nil {
t.Fatal(err)
}
if want := push20 + cold; res.UsedRegularGas != want {
t.Fatalf("cold EXTCODEHASH = %d, want %d", res.UsedRegularGas, want)
}
})
t.Run("EXTCODESIZE adds WARM_ACCESS", func(t *testing.T) {
code := append(pushAddr(), 0x3b) // EXTCODESIZE
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
if err != nil {
t.Fatal(err)
}
if want := push20 + cold + warm; res.UsedRegularGas != want {
t.Fatalf("cold EXTCODESIZE = %d, want %d", res.UsedRegularGas, want)
}
})
t.Run("EXTCODECOPY adds WARM_ACCESS", func(t *testing.T) {
// PUSH1 0 (length); PUSH1 0 (codeOffset); PUSH1 0 (destOffset); PUSH20 addr; EXTCODECOPY
code := []byte{0x60, 0x00, 0x60, 0x00, 0x60, 0x00}
code = append(code, pushAddr()...)
code = append(code, 0x3c) // EXTCODECOPY
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
if err != nil {
t.Fatal(err)
}
// three PUSH1 + one PUSH20 = 12 gas, zero-length copy => no memory/copy gas.
if want := uint64(12) + cold + warm; res.UsedRegularGas != want {
t.Fatalf("cold EXTCODECOPY = %d, want %d", res.UsedRegularGas, want)
}
})
}
// TestEIP8038SelfdestructAccountWrite checks that SELFDESTRUCT sending a positive
// balance to an empty account is charged the cold access, an additional
// ACCOUNT_WRITE (regular) and GAS_NEW_ACCOUNT (state).
func TestEIP8038SelfdestructAccountWrite(t *testing.T) {
beneficiary := common.BytesToAddress([]byte("fresh-beneficiary"))
// PUSH20 beneficiary; SELFDESTRUCT
code := append([]byte{0x73}, beneficiary.Bytes()...)
code = append(code, 0xff)
// Fund the contract so it sends a positive balance on self-destruct.
fundSelf := func(db *state.StateDB, self common.Address) {
db.AddBalance(self, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
}
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), fundSelf)
if err != nil {
t.Fatal(err)
}
const push20 = uint64(3)
wantReg := push20 + params.SelfdestructGasEIP150 + params.ColdAccountAccessAmsterdam + params.AccountWriteAmsterdam
if res.UsedRegularGas != wantReg {
t.Fatalf("regular gas = %d, want %d", res.UsedRegularGas, wantReg)
}
if want := int64(params.AccountCreationSize * params.CostPerStateByte); res.UsedStateGas != want {
t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want)
}
}

View file

@ -43,7 +43,8 @@ var activators = map[int]func(*JumpTable){
7939: enable7939,
8024: enable8024,
7843: enable7843,
8037: enable8037,
8037: enable8037And8038,
8038: enable8037And8038,
}
// EnableEIP enables the given EIP on the config.
@ -584,13 +585,30 @@ func enable7843(jt *JumpTable) {
}
}
// enable8037 enables the multidimensional-metering as specified in EIP-8037.
func enable8037(jt *JumpTable) {
jt[CREATE].constantGas = params.CreateGasAmsterdam
// enable8037And8038 enables EIP-8037 (multidimensional state-gas metering)
// together with EIP-8038 (state-access gas cost update).
func enable8037And8038(jt *JumpTable) {
jt[CREATE].constantGas = params.CreateAccessAmsterdam
jt[CREATE].dynamicGas = gasCreateEip8037
jt[CREATE2].constantGas = params.CreateGasAmsterdam
jt[CREATE2].constantGas = params.CreateAccessAmsterdam
jt[CREATE2].dynamicGas = gasCreate2Eip8037
jt[CALL].dynamicGas = gasCallEIP8037
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037
jt[SSTORE].dynamicGas = gasSStore8037
// Storage-access opcodes
jt[SLOAD].dynamicGas = gasSLoad8038
jt[SSTORE].dynamicGas = gasSStore8037And8038
// Account-access opcodes
jt[BALANCE].dynamicGas = gasEip8038AccountCheck
jt[EXTCODEHASH].dynamicGas = gasEip8038AccountCheck
jt[EXTCODESIZE].dynamicGas = gasExtCodeSize8038
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopy8038
// Call family
jt[CALL].dynamicGas = gasCall8038
jt[CALLCODE].dynamicGas = gasCallCode8038
jt[STATICCALL].dynamicGas = gasStaticCall8038
jt[DELEGATECALL].dynamicGas = gasDelegateCall8038
// SELFDESTRUCT
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037And8038
}

View file

@ -478,6 +478,28 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
return gas, nil
}
// gasCallCodeIntrinsic8038 mirrors gasCallCodeIntrinsic but charges the
// re-priced CALL_VALUE (ACCOUNT_WRITE + CALL_STIPEND) on value transfers per
// EIP-8038. CALLCODE executes in the caller's context, so it never creates a
// new account and has no state-gas component.
func gasCallCodeIntrinsic8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
var (
gas uint64
overflow bool
)
if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferAmsterdam
}
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
@ -576,10 +598,11 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}, nil
}
// regularGasCall8037 is the intrinsic gas calculator for CALL in Amsterdam.
// It computes memory expansion + value transfer gas but excludes new account
// creation, which is handled as state gas by the wrapper.
func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// regularGasCall8038 is the intrinsic regular-gas calculator for CALL in
// Amsterdam. It computes memory expansion plus the re-priced CALL_VALUE
// (ACCOUNT_WRITE + CALL_STIPEND) on value transfers, but excludes new account
// creation, which is handled as state gas by stateGasCall8037.
func regularGasCall8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
gas uint64
transfersValue = !stack.back(2).IsZero()
@ -593,7 +616,7 @@ func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}
var transferGas uint64
if transfersValue && !evm.chainRules.IsEIP4762 {
transferGas = params.CallValueTransferGas
transferGas = params.CallValueTransferAmsterdam
}
var overflow bool
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
@ -630,7 +653,9 @@ func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error
return gas, nil
}
func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
// gasSelfdestruct8037And8038 implements the SELFDESTRUCT gas charging under
// the EIP8037 and EIP-8038.
func gasSelfdestruct8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
@ -639,11 +664,11 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory
address = common.Address(stack.peek().Bytes20())
)
if !evm.StateDB.AddressInAccessList(address) {
// If the caller cannot afford the cost, this change will be rolled back
// If the caller cannot afford the cost, this change will be rolled back.
evm.StateDB.AddAddressToAccessList(address)
gas.RegularGas = params.ColdAccountAccessCostEIP2929
gas.RegularGas = params.ColdAccountAccessAmsterdam
}
// Check we have enough regular gas before we add the address to the BAL
// Check we have enough regular gas before we add the address to the BAL.
if contract.Gas.RegularGas < gas.RegularGas {
return gas, ErrOutOfGas
}
@ -654,79 +679,63 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory
//
// Funding such an account makes it permanent state growth and must be charged.
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas.RegularGas += params.AccountWriteAmsterdam
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
return gas, nil
}
func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
// gasSStore8037And8038 implements the SSTORE gas charging under EIP-8037 and
// EIP-8038.
func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
// If we fail the minimum gas availability invariant, fail (0)
// If we fail the minimum gas availability invariant, fail (0).
if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 {
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
}
// Gas sentry honoured, do the actual gas calculation based on the stored value
var (
y, x = stack.back(1), stack.peek()
slot = common.Hash(x.Bytes32())
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
cost GasCosts
value = common.Hash(y.Bytes32())
)
// Check slot presence in the access list
access := params.WarmStorageReadCostEIP2929
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929}
// If the caller cannot afford the cost, this change will be rolled back
access = params.ColdStorageAccessAmsterdam
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
}
value := common.Hash(y.Bytes32())
stateSet := params.StorageCreationSize * evm.Context.CostPerStateByte
if current == value { // noop (1)
// EIP 2200 original clause:
// return params.SloadGasEIP2200, nil
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
return GasCosts{RegularGas: access}, nil
}
if original == current {
if original == current { // first change of the slot (2.1)
if original == (common.Hash{}) { // create slot (2.1.1)
return GasCosts{
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
RegularGas: access + params.StorageWriteAmsterdam,
StateGas: stateSet,
}, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
evm.StateDB.AddRefund(params.StorageClearRefundAmsterdam)
}
// EIP-2200 original clause:
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2)
return GasCosts{RegularGas: access + params.StorageWriteAmsterdam}, nil // write existing slot (2.1.2)
}
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529)
evm.StateDB.SubRefund(params.StorageClearRefundAmsterdam)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
evm.StateDB.AddRefund(params.StorageClearRefundAmsterdam)
}
}
if original == value {
if original == value { // reset to original value (2.2.2)
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
// EIP-8037 point (2): refund state gas directly to the reservoir
// at the SSTORE restoration point (0→x→0 in same tx); not to the
// refund counter, which is capped at gas_used/5.
contract.Gas.RefundState(params.StorageCreationSize * evm.Context.CostPerStateByte)
// Regular portion of the refund still goes through the refund counter.
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
} else { // reset to original existing slot (2.2.2.2)
// EIP 2200 Original clause:
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
contract.Gas.RefundState(stateSet)
}
evm.StateDB.AddRefund(params.StorageWriteAmsterdam)
}
// EIP-2200 original clause:
//return params.SloadGasEIP2200, nil // dirty update (2.2)
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2)
return GasCosts{RegularGas: access}, nil // dirty update (2.2)
}

View file

@ -99,9 +99,9 @@ func newVerkleInstructionSet() JumpTable {
func newAmsterdamInstructionSet() JumpTable {
instructionSet := newOsakaInstructionSet()
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase)
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
enable8037And8038(&instructionSet) // EIP-8037 (state-gas metering) + EIP-8038 (state-access repricing)
return validate(instructionSet)
}

View file

@ -101,16 +101,25 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
loc := stack.peek()
slot := common.Hash(loc.Bytes32())
// Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
// If the caller cannot afford the cost, this change will be rolled back
// If he does afford it, we can skip checking the same thing later on, during execution
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
return GasCosts{RegularGas: params.ColdSloadCostEIP2929}, nil
}
return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil
}
// gasSLoad8038 mirrors gasSLoadEIP2929 but uses the EIP-8038 COLD_STORAGE_ACCESS
// for a cold slot.
func gasSLoad8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
loc := stack.peek()
slot := common.Hash(loc.Bytes32())
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
return GasCosts{RegularGas: params.ColdStorageAccessAmsterdam}, nil
}
return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil
}
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
// EIP spec:
// > If the target is not in accessed_addresses,
@ -137,6 +146,34 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
return GasCosts{RegularGas: gas}, nil
}
// gasExtCodeCopy8038 mirrors gasExtCodeCopyEIP2929 but uses the EIP-8038
// COLD_ACCOUNT_ACCESS and adds an extra WARM_ACCESS for the second
// database read EXTCODECOPY performs.
func gasExtCodeCopy8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
// memory expansion first (dynamic part of pre-2929 implementation)
gasCost, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
gas := gasCost.RegularGas
addr := common.Address(stack.peek().Bytes20())
// Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(addr)
var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessAmsterdam-params.WarmStorageReadCostEIP2929); overflow {
return GasCosts{}, ErrGasUintOverflow
}
}
// Additional WARM_ACCESS for the second database read (contract code).
var overflow bool
if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
return GasCosts{}, ErrGasUintOverflow
}
return GasCosts{RegularGas: gas}, nil
}
// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list.
// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it
// is also using 'warm' as constant factor.
@ -156,6 +193,32 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return GasCosts{}, nil
}
// gasEip8038AccountCheck mirrors gasEip2929AccountCheck but uses the EIP-8038
// COLD_ACCOUNT_ACCESS. Used by BALANCE and EXTCODEHASH.
func gasEip8038AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
addr := common.Address(stack.peek().Bytes20())
// Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) {
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(addr)
// The warm storage read cost is already charged as constantGas
return GasCosts{RegularGas: params.ColdAccountAccessAmsterdam - params.WarmStorageReadCostEIP2929}, nil
}
return GasCosts{}, nil
}
// gasExtCodeSize8038 prices EXTCODESIZE under EIP-8038: the gasEip8038AccountCheck
// surcharge plus an additional WARM_ACCESS for the second database read (code size).
func gasExtCodeSize8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
cost, err := gasEip8038AccountCheck(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
// Additional WARM_ACCESS for the second database read (contract size).
cost.RegularGas += params.WarmStorageReadCostEIP2929
return cost, nil
}
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
addr := common.Address(stack.back(addressPosition).Bytes20())
@ -266,11 +329,10 @@ func recordDelegationAccess(evm *EVM, target common.Address) {
}
var (
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(regularGasCall8037, stateGasCall8037)
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic, params.ColdAccountAccessCostEIP2929)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic, params.ColdAccountAccessCostEIP2929)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic, params.ColdAccountAccessCostEIP2929)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic, params.ColdAccountAccessCostEIP2929)
)
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
@ -285,15 +347,24 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
}
func gasCallEIP8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
innerGasCall8038 = makeCallVariantGasCallEIP8037(regularGasCall8038, stateGasCall8037, params.ColdAccountAccessAmsterdam)
gasCallCode8038 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic8038, params.ColdAccountAccessAmsterdam)
gasDelegateCall8038 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic, params.ColdAccountAccessAmsterdam)
gasStaticCall8038 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic, params.ColdAccountAccessAmsterdam)
)
// gasCall8038 prices CALL for Amsterdam, guarding against value transfers in a
// read-only context before delegating to the state-gas-aware wrapper.
func gasCall8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
transfersValue := !stack.back(2).IsZero()
if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection
}
return innerGasCallEIP8037(evm, contract, stack, mem, memorySize)
return innerGasCall8038(evm, contract, stack, mem, memorySize)
}
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc, coldCost uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
eip2929Cost uint64
@ -308,7 +379,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form
// of a constant cost, so the cost to charge for cold access, if any,
// is Cold - Warm
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929
// Charge the remaining difference here already, to correctly calculate
// available gas for call
@ -339,7 +410,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
eip7702Cost = params.WarmStorageReadCostEIP2929
} else {
evm.StateDB.AddAddressToAccessList(target)
eip7702Cost = params.ColdAccountAccessCostEIP2929
eip7702Cost = coldCost
}
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
@ -388,7 +459,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// It extends the EIP-7702 pattern with state gas handling and GasUsed tracking.
// intrinsicFunc computes the regular gas (memory + transfer, no new account creation).
// stateGasFunc computes the state gas (new account creation as state gas).
func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc) gasFunc {
func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc, coldCost uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
eip2929Cost uint64
@ -398,7 +469,7 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
// EIP-2929 cold access check.
if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(addr)
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929
if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
@ -423,7 +494,7 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
eip7702Cost = params.WarmStorageReadCostEIP2929
} else {
evm.StateDB.AddAddressToAccessList(target)
eip7702Cost = params.ColdAccountAccessCostEIP2929
eip7702Cost = coldCost
}
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas

View file

@ -88,7 +88,6 @@ const (
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation
CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately.
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
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.
@ -101,7 +100,14 @@ const (
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
// RegularPerAuthBaseCost is defined in EIP-8037 as the sum of:
//
// - Calldata cost: 1,616 (101 bytes × 16)
// - Recovering authority address (ecRecover)
// - Reading nonce and code of authority (cold access)
// - Storing values in already warm account: 2 x WARM_ACCESS
RegularPerAuthBaseCost uint64 = 7500
// EIP-2780: resource-based intrinsic transaction gas.
TxBaseCost2780 uint64 = 12000
@ -110,6 +116,17 @@ const (
TxValueCost2780 uint64 = 4244
TransferLogCost2780 uint64 = 1756
// EIP-8038: state-access gas cost update (Amsterdam).
ColdAccountAccessAmsterdam uint64 = 3000 // COLD_ACCOUNT_ACCESS: cold touch of an account
AccountWriteAmsterdam uint64 = 8000 // ACCOUNT_WRITE: surcharge for first-time write to an account
CallValueTransferAmsterdam uint64 = 10300 // CALL_VALUE = ACCOUNT_WRITE + CallStipend (2300)
ColdStorageAccessAmsterdam uint64 = 3000 // COLD_STORAGE_ACCESS: cold touch of a storage slot
StorageWriteAmsterdam uint64 = 10000 // STORAGE_WRITE: surcharge for first-time write to a storage slot
StorageClearRefundAmsterdam uint64 = 12480 // STORAGE_CLEAR_REFUND: refund for clearing a storage slot
CreateAccessAmsterdam uint64 = 11000 // CREATE_ACCESS = ACCOUNT_WRITE + COLD_STORAGE_ACCESS
TxAccessListAddressGasAmsterdam uint64 = 3000 // ACCESS_LIST_ADDRESS_COST
TxAccessListStorageKeyGasAmsterdam uint64 = 3000 // ACCESS_LIST_STORAGE_KEY_COST
// These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine)