core: implement EIP-2780

This commit is contained in:
Gary Rong 2026-06-24 11:18:33 +08:00 committed by MariusVanDerWijden
parent c6a4fd8ca1
commit 7d5b36f86c
No known key found for this signature in database
11 changed files with 347 additions and 31 deletions

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/tests"
"github.com/holiman/uint256"
"github.com/urfave/cli/v2"
)
@ -133,7 +134,7 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), uint256.MustFromBig(tx.Value()), rules, params.CostPerStateByte)
if err != nil {
r.Error = err
results = append(results, r)

View file

@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes)
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte)
cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte)
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {

View file

@ -64,12 +64,12 @@ var (
func TestProcessUBT(t *testing.T) {
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
signer = types.LatestSigner(testUBTChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain

208
core/eip2780_test.go Normal file
View file

@ -0,0 +1,208 @@
// 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/>.
package core
import (
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
// TestEIP2780Intrinsic checks the intrinsic-gas decomposition.
func TestEIP2780Intrinsic(t *testing.T) {
var (
from = common.HexToAddress("0x1111111111111111111111111111111111111111")
to = common.HexToAddress("0x2222222222222222222222222222222222222222")
)
cases := []struct {
name string
to *common.Address
value *uint256.Int
want vm.GasCosts
}{
{
name: "self-transfer",
to: &from,
value: uint256.NewInt(1),
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
},
{
name: "self-transfer/zero-value",
to: &from,
value: uint256.NewInt(0),
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
},
{
name: "zero-value call",
to: &to,
value: uint256.NewInt(0),
// TxBaseCost + ColdAccountAccess = 15,000
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780},
},
{
name: "value transfer to existing EOA",
to: &to,
value: uint256.NewInt(1),
// TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
params.TxValueCost2780 + params.TransferLogCost2780},
},
{
name: "contract creation, value = 0",
to: nil,
value: uint256.NewInt(0),
// TxBaseCost + CreateAccess = 23,000 regular, plus one account creation in state.
want: vm.GasCosts{
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
{
name: "contract creation, value > 0",
to: nil,
value: uint256.NewInt(1),
// TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular, plus account creation.
want: vm.GasCosts{
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := IntrinsicGas(nil, nil, nil, from, tc.to, tc.value, rules8037, params.CostPerStateByte)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tc.want {
t.Fatalf("gas mismatch: got %+v, want %+v", got, tc.want)
}
})
}
}
// TestEIP2780Gas checks every "Transaction reference case" in
// the EIP-2780 specification end-to-end, asserting the two-dimensional charge
// (intrinsic + top-level + execution) recorded in the block gas pool.
func TestEIP2780Gas(t *testing.T) {
const (
cold = params.ColdAccountAccess2780
base = params.TxBaseCost2780
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
)
var (
existingEOA = common.HexToAddress("0xe0a0000000000000000000000000000000000001")
stopContract = common.HexToAddress("0xc0de000000000000000000000000000000000001")
delegated = common.HexToAddress("0xde1e000000000000000000000000000000000001")
emptyTarget = common.HexToAddress("0x7a76000000000000000000000000000000000001") // never allocated
freshEOA = common.HexToAddress("0xbeef000000000000000000000000000000000001") // never allocated
)
// Shared world: a funded EOA, a STOP contract and an account delegated to a
// non-existent (codeless) target. The delegation target is intentionally
// absent so resolving it executes no code.
base7702 := types.GenesisAlloc{
existingEOA: {Balance: big.NewInt(1)},
stopContract: {Code: []byte{0x00}}, // STOP
delegated: {Code: types.AddressToDelegation(emptyTarget)},
}
// valueCreateTx builds a contract-creation transaction carrying value.
valueCreateTx := func(value int64) *types.Transaction {
return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
ChainID: cfg8037.ChainID, Nonce: 0, To: nil, Value: big.NewInt(value),
Gas: 300_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0),
})
}
cases := []struct {
name string
tx *types.Transaction
wantRegular, wantState uint64
}{
// case 1: ETH transfer to self.
{"self-transfer", callTx(0, senderAddr, 1, 100_000, nil), base, 0},
// case 2: no-transfer to an existing EOA.
{"zero-value/eoa", callTx(0, existingEOA, 0, 100_000, nil), base + cold, 0},
// case 3: no-transfer to a contract.
{"zero-value/contract", callTx(0, stopContract, 0, 100_000, nil), base + cold, 0},
// case 4: ETH transfer to an existing EOA.
{"value/eoa", callTx(0, existingEOA, 1, 100_000, nil), base + cold + valueCst, 0},
// case 5: ETH transfer to a contract.
{"value/contract", callTx(0, stopContract, 1, 100_000, nil), base + cold + valueCst, 0},
// case 6: no-transfer to a 7702-delegated account.
{"zero-value/delegated", callTx(0, delegated, 0, 100_000, nil), base + 2*cold, 0},
// case 7: ETH transfer to a 7702-delegated account (no new-account charge).
{"value/delegated", callTx(0, delegated, 1, 100_000, nil), base + 2*cold + valueCst, 0},
// case 8: ETH transfer creating a new account.
{"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState},
// case 9: contract-creation transaction, value = 0.
{"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccess2780, newAccountState},
// case 10: contract-creation transaction, value > 0.
{"create/value", valueCreateTx(1), base + params.CreateAccess2780 + params.TransferLogCost2780, newAccountState},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
res, gp, err := applyMsg(t, mkState(senderAlloc(base7702)), tc.tx)
if err != nil {
t.Fatalf("consensus error: %v", err)
}
if res.Err != nil {
t.Fatalf("execution failed: %v", res.Err)
}
if gp.cumulativeRegular != tc.wantRegular {
t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, tc.wantRegular)
}
if gp.cumulativeState != tc.wantState {
t.Errorf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
}
})
}
}
// TestEIP2780NewAccountFunded verifies that a value transfer creating a new
// account both materializes and funds the recipient.
func TestEIP2780NewAccountFunded(t *testing.T) {
fresh := common.HexToAddress("0xbeef000000000000000000000000000000000002")
sdb := mkState(senderAlloc(nil))
if _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 300_000, nil)); err != nil {
t.Fatal(err)
}
if !sdb.Exist(fresh) || sdb.GetBalance(fresh).Cmp(uint256.NewInt(1)) != 0 {
t.Fatalf("recipient not funded: exist=%v balance=%v", sdb.Exist(fresh), sdb.GetBalance(fresh))
}
}
// TestEIP2780InsufficientGasForCallCharge verifies that a value transfer
// creating a new account is rejected when the gas limit only covers the 21,000
// intrinsic base and not the additional new-account state gas charged before the
// call executes.
func TestEIP2780InsufficientGasForCallCharge(t *testing.T) {
fresh := common.HexToAddress("0xbeef000000000000000000000000000000000003")
sdb := mkState(senderAlloc(nil))
_, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 21_000, nil))
if !errors.Is(err, ErrEIP2780CallCharge) {
t.Fatalf("expected ErrEIP2780CallCharge, got %v", err)
}
if sdb.Exist(fresh) {
t.Fatal("recipient should not be created when the call charge cannot be paid")
}
}

View file

@ -66,11 +66,6 @@ var (
// have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
// funds to cover the transfer, but not enough to pay for witness access/modification
// costs for the transaction
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
@ -86,6 +81,10 @@ var (
// than required for the data floor cost.
ErrFloorDataGas = errors.New("insufficient gas for floor data gas cost")
// ErrEIP2780CallCharge is returned if the transaction doesn't have sufficient
// gas to cover the cost of EIP-2780 call charge.
ErrEIP2780CallCharge = errors.New("insufficient gas for EIP-2780 call charge")
// ErrTxTypeNotSupported is returned if a transaction is not supported in the
// current network configuration.
ErrTxTypeNotSupported = types.ErrTxTypeNotSupported

View file

@ -88,10 +88,10 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
blockAccessList = bal.NewConstructionBlockAccessList()
)
defer evm.Release()
if jumpDestCache != nil {
evm.SetJumpDestCache(jumpDestCache)
}
// Run the pre-execution system calls
blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()))

View file

@ -68,16 +68,15 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
isContractCreation := to == nil
// Set the starting gas for the raw transaction
var gas vm.GasCosts
if isContractCreation && rules.IsHomestead {
if rules.IsAmsterdam {
gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
gas.StateGas = params.AccountCreationSize * costPerStateByte
} else {
gas.RegularGas = params.TxGasContractCreation
}
if rules.IsAmsterdam {
gas = intrinsicBaseGasEIP2780(from, to, value, costPerStateByte)
} else if isContractCreation && rules.IsHomestead {
gas.RegularGas = params.TxGasContractCreation
} else {
gas.RegularGas = params.TxGas
}
@ -151,6 +150,39 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
return gas, nil
}
// intrinsicBaseGasEIP2780 computes the EIP-2780 intrinsic base cost.
func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int, costPerStateByte uint64) vm.GasCosts {
var (
isContractCreation = to == nil
isSelfTransfer = to != nil && *to == from
hasValue = value != nil && !value.IsZero()
)
// tx.sender: signature recovery plus the sender account access and write.
gas := vm.GasCosts{RegularGas: params.TxBaseCost2780}
// tx.to charge.
switch {
case isSelfTransfer:
// The recipient account is already accessed and written as the sender.
case isContractCreation:
gas.RegularGas += params.CreateAccess2780
gas.StateGas += params.AccountCreationSize * costPerStateByte
default:
gas.RegularGas += params.ColdAccountAccess2780
}
// tx.value charge.
switch {
case !hasValue || isSelfTransfer:
// No transfer log and no recipient balance write.
case isContractCreation:
gas.RegularGas += params.TransferLogCost2780
default:
gas.RegularGas += params.TransferLogCost2780 + params.TxValueCost2780
}
return gas
}
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
var (
@ -622,7 +654,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
contractCreation = msg.To == nil
floorDataGas uint64
)
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
if err != nil {
return nil, err
}
@ -714,6 +746,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
st.state.AddAddressToAccessList(addr)
}
// EIP-2780: charge the transaction's top-level recipient costs.
if rules.IsAmsterdam && !st.chargeCallRecipientEIP2780(value) {
return nil, fmt.Errorf("%w: address %v", ErrEIP2780CallCharge, msg.To.Hex())
}
// Execute the transaction's call.
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
@ -786,6 +822,39 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}, nil
}
// chargeCallRecipientEIP2780 applies the EIP-2780 transaction top-level gas costs for
// a message-call transaction, charged before any opcode executes:
//
// - if the recipient is EIP-161 empty and the transaction carries value, charge
// for account creation.
//
// - if the recipient is an EIP-7702 delegated account, resolving the delegation
// loads the target's code, charged an additional cold account access in
// regular gas.
func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
var (
cost vm.GasCosts
to = *st.msg.To
)
if !value.IsZero() && st.state.Empty(to) {
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
}
if _, ok := types.ParseDelegation(st.state.GetCode(to)); ok {
cost.RegularGas += params.ColdAccountAccess2780
}
if cost == (vm.GasCosts{}) {
return true
}
prior, ok := st.gasRemaining.Charge(cost)
if !ok {
return false
}
if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
}
return true
}
// settleGas finalizes the per-tx gas accounting after EVM execution:
//
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
func TestFloorDataGas(t *testing.T) {
@ -155,6 +156,7 @@ func TestIntrinsicGas(t *testing.T) {
isEIP2028 bool
isEIP3860 bool
isAmsterdam bool
value *uint256.Int
want vm.GasCosts
}{
{
@ -237,8 +239,9 @@ func TestIntrinsicGas(t *testing.T) {
},
isEIP2028: true,
isAmsterdam: true,
// base access-list charge + EIP-7981 extra
want: vm.GasCosts{RegularGas: params.TxGas +
// 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*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
},
@ -259,10 +262,10 @@ func TestIntrinsicGas(t *testing.T) {
isHomestead: true,
isEIP2028: true,
isAmsterdam: true,
// EIP-8037: creation regular gas is TxGas + CreateGasAmsterdam (not TxGasContractCreation),
// and account-creation cost is moved to state gas.
// EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000),
// and account-creation cost is charged as state gas.
want: vm.GasCosts{
RegularGas: params.TxGas + params.CreateGasAmsterdam,
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
@ -275,7 +278,7 @@ func TestIntrinsicGas(t *testing.T) {
isEIP3860: true, // Shanghai gates init-code word gas
isAmsterdam: true,
want: vm.GasCosts{
RegularGas: params.TxGas + params.CreateGasAmsterdam +
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
@ -292,7 +295,7 @@ func TestIntrinsicGas(t *testing.T) {
isEIP3860: true,
isAmsterdam: true,
want: vm.GasCosts{
RegularGas: params.TxGas + params.CreateGasAmsterdam +
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
@ -314,7 +317,7 @@ func TestIntrinsicGas(t *testing.T) {
// regular: TxAuthTupleRegularGas (7500) per auth
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
want: vm.GasCosts{
RegularGas: params.TxGas +
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
100*params.TxDataNonZeroGasEIP2028 +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
@ -322,6 +325,28 @@ func TestIntrinsicGas(t *testing.T) {
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
},
},
{
name: "amsterdam/value-transfer-call",
isEIP2028: true,
isAmsterdam: true,
value: uint256.NewInt(1),
// EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000.
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
params.TransferLogCost2780 + params.TxValueCost2780},
},
{
name: "amsterdam/value-bearing-contract-creation",
creation: true,
isHomestead: true,
isEIP2028: true,
isAmsterdam: true,
value: uint256.NewInt(1),
// EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756, plus account-creation state gas.
want: vm.GasCosts{
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -331,8 +356,12 @@ func TestIntrinsicGas(t *testing.T) {
IsShanghai: tt.isEIP3860,
IsAmsterdam: tt.isAmsterdam,
}
var to *common.Address
if !tt.creation {
to = &addr1
}
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
tt.creation, rules, params.CostPerStateByte)
common.Address{}, to, tt.value, rules, params.CostPerStateByte)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
var (
@ -116,7 +117,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return core.ErrTipAboveFeeCap
}
// Make sure the transaction is signed properly
if _, err := types.Sender(signer, tx); err != nil {
from, err := types.Sender(signer, tx)
if err != nil {
return fmt.Errorf("%w: %v", ErrInvalidSender, err)
}
// Limit nonce to 2^64-1 per EIP-2681
@ -125,7 +127,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), uint256.MustFromBig(tx.Value()), rules, params.CostPerStateByte)
if err != nil {
return err
}

View file

@ -103,6 +103,13 @@ const (
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
// EIP-2780: resource-based intrinsic transaction gas.
TxBaseCost2780 uint64 = 12000
ColdAccountAccess2780 uint64 = 3000
CreateAccess2780 uint64 = 11000
TxValueCost2780 uint64 = 4244
TransferLogCost2780 uint64 = 1756
// 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)

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
// TransactionTest checks RLP decoding and sender derivation of transactions.
@ -81,7 +82,7 @@ func (tt *TransactionTest) Run() error {
return
}
// Intrinsic cost
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), uint256.MustFromBig(tx.Value()), rules, params.CostPerStateByte)
if err != nil {
return
}