mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-02 19:21:16 +00:00
core: implement EIP-8038 (#35216)
Implements https://eips.ethereum.org/EIPS/eip-8038
This commit is contained in:
parent
59e89e81e5
commit
dd8dd1520f
10 changed files with 573 additions and 99 deletions
110
core/eip8038_test.go
Normal file
110
core/eip8038_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
237
core/vm/eip8038_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue