mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-25 07:56:16 +00:00
214 lines
8.3 KiB
Go
214 lines
8.3 KiB
Go
// 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 (
|
|
"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, whose gas limit only covers the 21,000 intrinsic base
|
|
// and not the additional new-account state gas charged before the call executes,
|
|
// halts out of gas. The transaction stays valid (no consensus error) but
|
|
// execution fails and the recipient is not created.
|
|
func TestEIP2780InsufficientGasForCallCharge(t *testing.T) {
|
|
fresh := common.HexToAddress("0xbeef000000000000000000000000000000000003")
|
|
sdb := mkState(senderAlloc(nil))
|
|
res, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 21_000, nil))
|
|
if err != nil {
|
|
t.Fatalf("transaction should remain valid: %v", err)
|
|
}
|
|
if res.Err != vm.ErrOutOfGas {
|
|
t.Fatalf("expected out of gas, got %v", res.Err)
|
|
}
|
|
if res.UsedGas != 21_000 {
|
|
t.Fatalf("expected used gas, got %v", res.UsedGas)
|
|
}
|
|
if sdb.Exist(fresh) {
|
|
t.Fatal("recipient should not be created when the call charge cannot be paid")
|
|
}
|
|
}
|