diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 7207cad41d..805b32ee02 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -140,15 +140,15 @@ func Transaction(ctx *cli.Context) error { value = uint256.NewInt(1) } rules := chainConfig.Rules(common.Big0, true, 0) - cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules, params.CostPerStateByte) + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules) if err != nil { r.Error = err results = append(results, r) continue } - r.IntrinsicGas = cost.RegularGas - if tx.Gas() < cost.RegularGas { - r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas) + r.IntrinsicGas = cost + if tx.Gas() < cost { + r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost) results = append(results, r) continue } diff --git a/core/bench_test.go b/core/bench_test.go index 79584309da..47b991e5a3 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -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, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte) + cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{}) signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { @@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { Nonce: gen.TxNonce(benchRootAddr), To: &toaddr, Value: big.NewInt(1), - Gas: cost.RegularGas, + Gas: cost, Data: data, GasPrice: gasPrice, }) diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go index 9cbb489c4e..e275c07f3e 100644 --- a/core/bintrie_witness_test.go +++ b/core/bintrie_witness_test.go @@ -65,12 +65,12 @@ var ( func TestProcessUBT(t *testing.T) { var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, 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}) // 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, common.Address{}, nil, nil, 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}) signer = types.LatestSigner(testUBTChainConfig) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain @@ -102,11 +102,11 @@ func TestProcessUBT(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas - contractCreationCost := intrinsicContractCreationGas.RegularGas + + contractCreationCost := intrinsicContractCreationGas + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */ 739 /* execution costs */ - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas.RegularGas + + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ diff --git a/core/eip2780_test.go b/core/eip2780_test.go index 09205cc31e..d2bb39bf50 100644 --- a/core/eip2780_test.go +++ b/core/eip2780_test.go @@ -38,19 +38,19 @@ func TestEIP2780Intrinsic(t *testing.T) { to *common.Address value *uint256.Int auths []types.SetCodeAuthorization - want vm.GasCosts + want uint64 }{ { name: "self-transfer", to: &from, value: uint256.NewInt(1), - want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000 + want: params.TxBaseCost2780, // 12,000 }, { name: "self-transfer/zero-value", to: &from, value: uint256.NewInt(0), - want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000 + want: params.TxBaseCost2780, // 12,000 }, { name: "zero-value call", @@ -58,15 +58,15 @@ func TestEIP2780Intrinsic(t *testing.T) { value: uint256.NewInt(0), // TxBaseCost + ColdAccountAccess = 15,000; the recipient touch is // charged at the cold rate unconditionally at the intrinsic phase. - want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam}, + want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam, }, { 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.ColdAccountAccessAmsterdam + - params.TxValueCost2780 + params.TransferLogCost2780}, + want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + + params.TxValueCost2780 + params.TransferLogCost2780, }, { name: "contract creation, value = 0", @@ -75,18 +75,14 @@ func TestEIP2780Intrinsic(t *testing.T) { // TxBaseCost + CreateAccess = 23,000 regular. The new-account state // charge depends on whether the deployment target exists and is // charged at runtime, not intrinsically. - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam, - }, + want: params.TxBaseCost2780 + params.CreateAccessAmsterdam, }, { name: "contract creation, value > 0", to: nil, value: uint256.NewInt(1), // TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular. - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780, - }, + want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780, }, { name: "value transfer with authorizations", @@ -95,13 +91,13 @@ func TestEIP2780Intrinsic(t *testing.T) { auths: make([]types.SetCodeAuthorization, 3), // Each authorization adds the state-independent per-auth base // (cold authority access included). - want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + - params.TxValueCost2780 + params.TransferLogCost2780 + 3*params.RegularPerAuthBaseCost}, + want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + + params.TxValueCost2780 + params.TransferLogCost2780 + 3*params.RegularPerAuthBaseCost, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - got, err := IntrinsicGas(nil, nil, tc.auths, from, tc.to, tc.value, rules8037, params.CostPerStateByte) + got, err := IntrinsicGas(nil, nil, tc.auths, from, tc.to, tc.value, rules8037) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -426,9 +422,10 @@ func TestEIP2780HaltKeepsAuthStateGas(t *testing.T) { if code := sdb.GetCode(authority); len(code) == 0 { t.Fatal("delegation should persist through an in-frame halt") } - // The regular dimension is burned in full by the halt; the state dimension - // keeps the delegation's durable growth: a new account leaf plus the - // 23-byte indicator. + // The regular dimension is burned in full by the halt: the intrinsic cost + // plus the entire regular execution budget, which together equal the + // MaxTxGas cap. The state dimension keeps the delegation's durable + // growth: a new account leaf plus the 23-byte indicator. if gp.cumulativeRegular != params.MaxTxGas { t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, params.MaxTxGas) } diff --git a/core/eip8037_test.go b/core/eip8037_test.go index d4956ed138..ad01a90cce 100644 --- a/core/eip8037_test.go +++ b/core/eip8037_test.go @@ -106,8 +106,16 @@ func applyMsg(t *testing.T, sdb *state.StateDB, tx *types.Transaction) (*Executi res, err := st.execute() if err == nil && res != nil { assertPoolSane(t, res, gp) - limit := min(msg.GasLimit, params.MaxTxGas) - assertBudgetSane(t, vm.NewGasBudget(limit, msg.GasLimit-limit), st.gasRemaining) + // The budget is seeded with the post-intrinsic remainder: the intrinsic + // cost counts towards the MaxTxGas regular cap and the execution gas + // exceeding the regular budget forms the reservoir. + intrinsic, ierr := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules8037) + if ierr != nil { + t.Fatalf("intrinsic gas: %v", ierr) + } + executionGas := msg.GasLimit - intrinsic + gasLeft := min(params.MaxTxGas-intrinsic, executionGas) + assertBudgetSane(t, vm.NewGasBudget(gasLeft, executionGas-gasLeft), st.gasRemaining) } return res, gp, err } @@ -193,15 +201,12 @@ var ( // charge depends on whether the deployment target exists and is charged at // runtime (EIP-2780), not intrinsically. func TestCreateTxIntrinsicNoStateGas(t *testing.T) { - cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte) + cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037) if err != nil { t.Fatal(err) } - if cost.StateGas != 0 { - t.Fatalf("intrinsic state gas = %d, want 0", cost.StateGas) - } - if want := params.TxBaseCost2780 + params.CreateAccessAmsterdam; cost.RegularGas != want { - t.Fatalf("intrinsic regular gas = %d, want %d", cost.RegularGas, want) + if want := params.TxBaseCost2780 + params.CreateAccessAmsterdam; cost != want { + t.Fatalf("intrinsic gas = %d, want %d", cost, want) } } @@ -481,19 +486,16 @@ var delegate8037 = common.HexToAddress("0xde1e8a7e") // Intrinsic gas charges only the state-independent per-authorization base; // the state-dependent charges are applied at runtime (EIP-2780). func TestAuthIntrinsicBaseOnly(t *testing.T) { - cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte) + cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037) if err != nil { t.Fatal(err) } - if cost.StateGas != 0 { - t.Fatalf("intrinsic state gas = %d, want 0", cost.StateGas) - } // The recipient touch and the per-authorization authority access (priced // into RegularPerAuthBaseCost) are both charged at the cold rate // unconditionally at the intrinsic phase (EIP-2780). want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + params.RegularPerAuthBaseCost - if cost.RegularGas != want { - t.Fatalf("intrinsic regular gas = %d, want %d", cost.RegularGas, want) + if cost != want { + t.Fatalf("intrinsic gas = %d, want %d", cost, want) } } diff --git a/core/state_transition.go b/core/state_transition.go index 884d33e6b1..3e8ff8da42 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -69,24 +69,24 @@ 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, from common.Address, to *common.Address, value *uint256.Int, 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) (uint64, error) { isContractCreation := to == nil // Set the starting gas for the raw transaction - var gas vm.GasCosts + var gas uint64 if rules.IsAmsterdam { - gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value) + gas = intrinsicBaseGasEIP2780(from, to, value) } else if isContractCreation && rules.IsHomestead { - gas.RegularGas = params.TxGasContractCreation + gas = params.TxGasContractCreation } else { - gas.RegularGas = params.TxGas + gas = params.TxGas } // Add gas for authorizations if authList != nil { if rules.IsAmsterdam { - gas.RegularGas += uint64(len(authList)) * params.RegularPerAuthBaseCost + gas += uint64(len(authList)) * params.RegularPerAuthBaseCost } else { - gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas + gas += uint64(len(authList)) * params.CallNewAccountGas } } // Bump the required gas by the amount of transactional data @@ -101,22 +101,22 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set if rules.IsIstanbul { nonZeroGas = params.TxDataNonZeroGasEIP2028 } - if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz { - return vm.GasCosts{}, ErrGasUintOverflow + if (math.MaxUint64-gas)/nonZeroGas < nz { + return 0, ErrGasUintOverflow } - gas.RegularGas += nz * nonZeroGas + gas += nz * nonZeroGas - if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z { - return vm.GasCosts{}, ErrGasUintOverflow + if (math.MaxUint64-gas)/params.TxDataZeroGas < z { + return 0, ErrGasUintOverflow } - gas.RegularGas += z * params.TxDataZeroGas + gas += z * params.TxDataZeroGas if isContractCreation && rules.IsShanghai { lenWords := toWordSize(dataLen) - if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords { - return vm.GasCosts{}, ErrGasUintOverflow + if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { + return 0, ErrGasUintOverflow } - gas.RegularGas += lenWords * params.InitCodeWordGas + gas += lenWords * params.InitCodeWordGas } } // Add the gas for accessList @@ -131,14 +131,14 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set addressCost = params.TxAccessListAddressGasAmsterdam storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam } - if (math.MaxUint64-gas.RegularGas)/addressCost < addresses { - return vm.GasCosts{}, ErrGasUintOverflow + if (math.MaxUint64-gas)/addressCost < addresses { + return 0, ErrGasUintOverflow } - gas.RegularGas += addresses * addressCost - if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys { - return vm.GasCosts{}, ErrGasUintOverflow + gas += addresses * addressCost + if (math.MaxUint64-gas)/storageKeyCost < storageKeys { + return 0, ErrGasUintOverflow } - gas.RegularGas += storageKeys * storageKeyCost + gas += storageKeys * storageKeyCost // EIP-7981: access list data is charged in addition to the base charge. if rules.IsAmsterdam { @@ -146,21 +146,20 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte ) - if (math.MaxUint64-gas.RegularGas)/addressCost < addresses { - return vm.GasCosts{}, ErrGasUintOverflow + if (math.MaxUint64-gas)/addressCost < addresses { + return 0, ErrGasUintOverflow } - gas.RegularGas += addresses * addressCost - if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys { - return vm.GasCosts{}, ErrGasUintOverflow + gas += addresses * addressCost + if (math.MaxUint64-gas)/storageKeyCost < storageKeys { + return 0, ErrGasUintOverflow } - gas.RegularGas += storageKeys * storageKeyCost + gas += storageKeys * storageKeyCost } } return gas, nil } -// intrinsicBaseGasEIP2780 computes the regular-gas portion of the EIP-2780 -// intrinsic base cost: the per-resource decomposition of the legacy flat 21,000. +// intrinsicBaseGasEIP2780 computes the intrinsic base cost of the transaction. func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 { var ( isContractCreation = to == nil @@ -425,24 +424,16 @@ func (st *stateTransition) to() common.Address { return *st.msg.To } -// buyGas pre-pays gas from the sender's balance and initializes the -// transaction's gas budget. It is invoked at the tail of preCheck. +// buyGas pre-pays gas from the sender's balance. // // The balance requirement is the worst-case ETH the tx may need to lock // up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`, // plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance -// returns ErrInsufficientFunds. After the check, the sender is actually -// debited `msg.GasLimit × msg.GasPrice` (plus `blobGas × blobBaseFee` -// under Cancun), the cap-vs-tip differential is settled at tx end. +// returns ErrInsufficientFunds. // -// The gas budget is seeded into both `initialBudget` (frozen snapshot -// for tx-end accounting) and `gasRemaining` (live running balance): -// -// - Pre-Amsterdam: one-dimensional regular budget equal to -// `msg.GasLimit`; the state-gas reservoir is zero. -// - Amsterdam+ (EIP-8037): two-dimensional budget. Regular gas is -// capped at `MaxTxGas` (EIP-7825, 16_777_216); any excess from -// `msg.GasLimit` above that cap becomes the state-gas reservoir. +// After the check, the sender is actually debited `msg.GasLimit × msg.GasPrice` +// (plus `blobGas × blobBaseFee` under Cancun), the cap-vs-tip differential +// is settled at tx end. func (st *stateTransition) buyGas() error { mgval := new(uint256.Int).SetUint64(st.msg.GasLimit) _, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice) @@ -495,53 +486,60 @@ func (st *stateTransition) buyGas() error { if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) } - isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) - - // Reserve the gas budget in the block gas pool - var err error - if isAmsterdam { - err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit) - } else { - err = st.gp.CheckGasLegacy(st.msg.GasLimit) - } - if err != nil { - return err - } - - // After Amsterdam we limit the regular gas to 16M, the data gas to the transaction limit - limit := st.msg.GasLimit - if isAmsterdam { - limit = min(st.msg.GasLimit, params.MaxTxGas) - } - st.gasRemaining = vm.NewGasBudget(limit, st.msg.GasLimit-limit) - - if st.evm.Config.Tracer.HasGasHook() { - st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance) - } // Deduct the gas cost from the sender's balance st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy) return nil } +// initRuntimeGasBudget initializes the transaction's running gas budget with the +// gas remaining after the intrinsic cost has been deducted. +// +// After Amsterdam (EIP-8037) the intrinsic cost counts towards the EIP-7825 +// regular-gas cap: +// +// execution_gas = tx.gas - intrinsic_gas +// regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic_gas +// gas_left = min(regular_gas_budget, execution_gas) +// state_gas_reservoir = execution_gas - gas_left +func (st *stateTransition) initRuntimeGasBudget(rules params.Rules, intrinsicGas uint64) { + executionGas := st.msg.GasLimit - intrinsicGas + gasLeft := executionGas + if rules.IsAmsterdam { + gasLeft = min(params.MaxTxGas-intrinsicGas, executionGas) + } + st.gasRemaining = vm.NewGasBudget(gasLeft, executionGas-gasLeft) + + if st.evm.Config.Tracer.HasGasHook() { + st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: st.msg.GasLimit}, st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas) + } +} + // preCheck performs all pre-execution validation that does not require -// the EVM to run, then ends by calling buyGas to lock in the gas budget. +// the EVM to run, then ends by calling buyGas to lock ether for prepay. // It returns a consensus error if any of the following fail: // // - Sender nonce matches state and is not at 2^64-1 (EIP-2681). -// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam -// (the cap also bounds the regular dimension after Amsterdam, but -// it is enforced there via the two-dimensional budget in buyGas). +// +// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam. +// // - EIP-3607 sender-is-EOA, allowing accounts whose only code is an // EIP-7702 delegation designator. +// // - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+). +// // - Blob-tx structural checks: non-nil `To`, non-empty hash list, // valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+). +// // - Blob fee-cap not below the current blob base fee (Cancun+). +// // - EIP-7702 set-code-tx shape: non-nil `To` and non-empty // authorization list. +// // - EIP-3860 init code size cap on create transactions (Shanghai+, // with the raised Amsterdam cap). // +// - Insufficient block gas budget for including the transaction. +// // The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass // subsets of these checks for simulation paths (eth_call, eth_estimateGas). func (st *stateTransition) preCheck(rules params.Rules) error { @@ -640,6 +638,16 @@ func (st *stateTransition) preCheck(rules params.Rules) error { return err } } + // Reserve the gas budget in the block gas pool + var err error + if rules.IsAmsterdam { + err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit) + } else { + err = st.gp.CheckGasLegacy(st.msg.GasLimit) + } + if err != nil { + return err + } return st.buyGas() } @@ -665,22 +673,15 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { if err := st.preCheck(rules); err != nil { return nil, err } - - // Charge intrinsic gas (with overflow detection inside IntrinsicGas). - // Under Amsterdam the cost is two-dimensional and Charge debits both - // regular and state in one step. - cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte) + // Calculate the intrinsic gas of this transaction and make sure the gas limit + // is sufficient to cover that. + intrinsicGas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules) if err != nil { return nil, err } - prior, sufficient := st.gasRemaining.Charge(cost) - if !sufficient { - return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas) + if msg.GasLimit < intrinsicGas { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, intrinsicGas) } - if st.evm.Config.Tracer.HasGasHook() { - st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas) - } - // Validate the EIP-7623 calldata floor against the gas limit. The floor inflates // the total gas usage at tx end, so the gas limit must be sufficient to cover that. if rules.IsPrague { @@ -693,13 +694,15 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { if msg.GasLimit < floorDataGas { return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas) } - // In Amsterdam, the transaction gas limit is allowed to exceed - // params.MaxTxGas, but the calldata floor cost is capped by it. - if rules.IsAmsterdam && max(cost.RegularGas, floorDataGas) > params.MaxTxGas { - return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas) - } + } + // In Amsterdam, the transaction gas limit is allowed to exceed + // params.MaxTxGas, but the intrinsic cost and calldata floor + // cost is still capped by it. + if rules.IsAmsterdam && max(intrinsicGas, floorDataGas) > params.MaxTxGas { + return nil, fmt.Errorf("%w: intrinsic cost %v, floor: %v", ErrFloorDataGas, intrinsicGas, floorDataGas) } + // EIP-4762 setup if rules.IsEIP4762 { st.evm.AccessEvents.AddTxOrigin(msg.From) @@ -724,6 +727,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // - enable block-level accessList construction (EIP-7928) st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + // Initialize the running gas budget with the post-intrinsic remainder. + st.initRuntimeGasBudget(rules, intrinsicGas) + // Execute the top-most frame var ( ret []byte @@ -879,15 +885,11 @@ func (st *stateTransition) chargeRuntimeGas(cost vm.GasCosts) bool { } // chargeCallRecipientEIP2780 applies the EIP-2780 runtime charges for the -// top-level recipient of a message-call transaction, as the first frame is +// top-level recipient of a message-call transaction, before the first frame is // entered: // -// - the recipient touch was already charged at the cold rate unconditionally -// at the intrinsic phase (EIP-2780) and the account is warm from -// statedb.Prepare (EIP-2929), so no access charge or warming is due here; -// -// - if the recipient is EIP-161 non-existent and the transaction carries -// value, the durable state growth of the new account; +// - if the recipient is EIP-161 empty and the transaction carries value, +// the durable state growth of the new account; // // - if the recipient is an EIP-7702 delegated account, resolving the // delegation loads the target's code: a cold account access, or a warm @@ -899,18 +901,17 @@ func (st *stateTransition) chargeRuntimeGas(cost vm.GasCosts) bool { func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool { to := *st.msg.To - // This runs in the topmost frame before any bytecode executes, so unlike the - // execution-level checks which must use StateDB.Empty because SELFDESTRUCT can - // leave a transient EIP-161-empty account, no empty account can exist here, and - // !Exist is equivalent to Empty. - if !value.IsZero() && !st.state.Exist(to) { + // This runs in the topmost frame before any bytecode executes, non-existence + // is equivalent with EIP-161-empty, as no preceding operation can leave a + // transient EIP-161-empty account (such as zero-value transfer). + if !value.IsZero() && st.state.Empty(to) { if !st.chargeRuntimeGas(vm.GasCosts{StateGas: params.AccountCreationSize * st.evm.Context.CostPerStateByte}) { return false } } if target, delegated := types.ParseDelegation(st.state.GetCode(to)); delegated { // Pay the delegation-target access before the target is warmed and - // its code resolved (loaded) on frame entry. + // its code resolved (loaded). cost := vm.GasCosts{RegularGas: params.ColdAccountAccessAmsterdam} if st.state.AddressInAccessList(target) { cost.RegularGas = params.WarmAccountAccessAmsterdam @@ -958,7 +959,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g // EIP-8037: // tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir - // tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used + // tx_state_gas = tx_output.execution_state_gas_used // tx_regular_gas = max(tx_gas_used_before_refund - tx_state_gas, calldata_floor_gas_cost) gasLeft := st.gasRemaining.RegularGas + st.gasRemaining.StateGas gasUsedBeforeRefund := st.msg.GasLimit - gasLeft @@ -988,6 +989,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g peakUsed = max(peakUsed, floorDataGas) } + // Settle down the final gas consumption in the block-level pool if rules.IsAmsterdam { if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil { return 0, 0, err @@ -998,7 +1000,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g } } - // Refund leftover gas to the sender as ETH. + // Refund leftover gas to the sender if gasLeft > 0 { refund := new(uint256.Int).Mul(uint256.NewInt(gasLeft), st.msg.GasPrice) st.state.AddBalance(st.msg.From, refund, tracing.BalanceIncreaseGasReturn) @@ -1086,7 +1088,7 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se cost.RegularGas += params.AccountWriteAmsterdam } // Durable state growth of the new account - if !st.state.Exist(authority) { + if st.state.Empty(authority) { cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte } // Writing the 23-byte delegation indicator into a previously empty @@ -1100,11 +1102,7 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se // earlier state charge as it is no longer justified. // // Note that the refund and the charges above can never apply to the - // same authorization. The refund requires the indicator to have been - // created by a preceding authorization in this transaction, in which - // case the authority already exists, has already been written, and - // its indicator slot was empty at transaction start, so none of the - // charges is due. The ordering of the refund and the charge is + // same authorization. The ordering of the refund and the charge is // therefore irrelevant. if auth.Address == (common.Address{}) && curDelegated && !preDelegated { st.gasRemaining.RefundState(authBase) @@ -1113,7 +1111,6 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se return ErrOutOfGasRuntime } } - // Update nonce and account code. st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) diff --git a/core/state_transition_test.go b/core/state_transition_test.go index 9bec3c5bf7..ec473fadbc 100644 --- a/core/state_transition_test.go +++ b/core/state_transition_test.go @@ -22,7 +22,6 @@ import ( "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" ) @@ -158,50 +157,50 @@ func TestIntrinsicGas(t *testing.T) { isEIP3860 bool isAmsterdam bool value *uint256.Int - want vm.GasCosts + want uint64 }{ { name: "frontier/empty-call", - want: vm.GasCosts{RegularGas: params.TxGas}, + want: params.TxGas, }, { name: "frontier/contract-creation-pre-homestead", creation: true, isHomestead: false, // pre-homestead, contract creation still uses TxGas - want: vm.GasCosts{RegularGas: params.TxGas}, + want: params.TxGas, }, { name: "homestead/contract-creation", creation: true, isHomestead: true, - want: vm.GasCosts{RegularGas: params.TxGasContractCreation}, + want: params.TxGasContractCreation, }, { name: "frontier/non-zero-data", data: bytes.Repeat([]byte{0xff}, 100), // 100 nz bytes * 68 (frontier) - want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasFrontier}, + want: params.TxGas + 100*params.TxDataNonZeroGasFrontier, }, { name: "istanbul/non-zero-data", data: bytes.Repeat([]byte{0xff}, 100), isEIP2028: true, // 100 nz bytes * 16 (post-EIP2028) - want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasEIP2028}, + want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028, }, { name: "istanbul/zero-data", data: bytes.Repeat([]byte{0x00}, 100), isEIP2028: true, // 100 zero bytes * 4 - want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataZeroGas}, + want: params.TxGas + 100*params.TxDataZeroGas, }, { name: "istanbul/mixed-data", data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...), isEIP2028: true, - want: vm.GasCosts{RegularGas: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028}, + want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028, }, { name: "shanghai/init-code-word-gas", @@ -211,7 +210,7 @@ func TestIntrinsicGas(t *testing.T) { isEIP2028: true, isEIP3860: true, // TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2 - want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas}, + want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas, }, { name: "shanghai/init-code-non-multiple-of-32", @@ -220,7 +219,7 @@ func TestIntrinsicGas(t *testing.T) { isHomestead: true, isEIP2028: true, isEIP3860: true, - want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas}, + want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas, }, { name: "berlin/access-list", @@ -230,7 +229,7 @@ func TestIntrinsicGas(t *testing.T) { }, isEIP2028: true, // 2 addrs * 2400 + 3 keys * 1900 - want: vm.GasCosts{RegularGas: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas}, + want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas, }, { name: "amsterdam/access-list-extra-cost", @@ -244,9 +243,9 @@ func TestIntrinsicGas(t *testing.T) { // (15,000); the recipient touch is charged at the cold rate // unconditionally at the intrinsic phase. Plus base access-list // charge + EIP-7981 extra. - want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + + want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + 2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam + - 2*amsterdamAddressCost + 3*amsterdamStorageKeyCost}, + 2*amsterdamAddressCost + 3*amsterdamStorageKeyCost, }, { name: "prague/auth-list", @@ -257,7 +256,7 @@ func TestIntrinsicGas(t *testing.T) { }, isEIP2028: true, // 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple) - want: vm.GasCosts{RegularGas: params.TxGas + 3*params.CallNewAccountGas}, + want: params.TxGas + 3*params.CallNewAccountGas, }, { name: "amsterdam/contract-creation-empty", @@ -267,9 +266,7 @@ func TestIntrinsicGas(t *testing.T) { isAmsterdam: true, // EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000); // the new-account state charge is applied at runtime. - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam, - }, + want: params.TxBaseCost2780 + params.CreateAccessAmsterdam, }, { name: "amsterdam/contract-creation-init-code", @@ -279,10 +276,8 @@ func TestIntrinsicGas(t *testing.T) { isEIP2028: true, isEIP3860: true, // Shanghai gates init-code word gas isAmsterdam: true, - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam + - 64*params.TxDataZeroGas + 2*params.InitCodeWordGas, - }, + want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas, }, { name: "amsterdam/contract-creation-with-access-list", @@ -295,12 +290,10 @@ func TestIntrinsicGas(t *testing.T) { isEIP2028: true, isEIP3860: true, isAmsterdam: true, - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam + - 32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas + - 1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam + - 1*amsterdamAddressCost + 1*amsterdamStorageKeyCost, - }, + want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + + 32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas + + 1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam + + 1*amsterdamAddressCost + 1*amsterdamStorageKeyCost, }, { name: "amsterdam/combined", @@ -317,13 +310,11 @@ func TestIntrinsicGas(t *testing.T) { // access (priced into RegularPerAuthBaseCost) are both charged at the // cold rate unconditionally at the intrinsic phase; the account leaf // and indicator bytes are charged at runtime. - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + - 100*params.TxDataNonZeroGasEIP2028 + - 1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam + - 1*amsterdamAddressCost + 1*amsterdamStorageKeyCost + - 1*params.RegularPerAuthBaseCost, - }, + want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + + 100*params.TxDataNonZeroGasEIP2028 + + 1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam + + 1*amsterdamAddressCost + 1*amsterdamStorageKeyCost + + 1*params.RegularPerAuthBaseCost, }, { name: "amsterdam/value-transfer-call", @@ -331,8 +322,8 @@ func TestIntrinsicGas(t *testing.T) { isAmsterdam: true, value: uint256.NewInt(1), // EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000. - want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + - params.TransferLogCost2780 + params.TxValueCost2780}, + want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + + params.TransferLogCost2780 + params.TxValueCost2780, }, { name: "amsterdam/value-bearing-contract-creation", @@ -343,9 +334,7 @@ func TestIntrinsicGas(t *testing.T) { value: uint256.NewInt(1), // EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756; // the new-account state charge is applied at runtime. - want: vm.GasCosts{ - RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780, - }, + want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780, }, } for _, tt := range tests { @@ -361,7 +350,7 @@ func TestIntrinsicGas(t *testing.T) { to = &addr1 } got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList, - common.Address{}, to, tt.value, rules, params.CostPerStateByte) + common.Address{}, to, tt.value, rules) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/core/tracing/gen_gas_change_reason_stringer.go b/core/tracing/gen_gas_change_reason_stringer.go index fd297b4de3..cf4dbe0d26 100644 --- a/core/tracing/gen_gas_change_reason_stringer.go +++ b/core/tracing/gen_gas_change_reason_stringer.go @@ -30,21 +30,22 @@ func _() { _ = x[GasChangeTxDataFloor-19] _ = x[GasChangeRefundAccountCreation-20] _ = x[GasChangeTxRuntimeGas-21] + _ = x[GasChangeAccountCreation-22] _ = x[GasChangeIgnored-255] } const ( - _GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreationTxRuntimeGas" + _GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreationTxRuntimeGasAccountCreation" _GasChangeReason_name_1 = "Ignored" ) var ( - _GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 374, 386} + _GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 374, 386, 401} ) func (i GasChangeReason) String() string { switch { - case i <= 21: + case i <= 22: return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]] case i == 255: return _GasChangeReason_name_1 diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 6b2b7288c0..ba2428469b 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -480,6 +480,11 @@ const ( // costs of the transaction per EIP-2780. GasChangeTxRuntimeGas GasChangeReason = 21 + // GasChangeAccountCreation represents the conditional account-creation + // state cost charged in the creating frame when a CREATE/CREATE2 is about + // to create a new account (EIP-8037). + GasChangeAccountCreation GasChangeReason = 22 + // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // it will be "manually" tracked by a direct emit of the gas change event. GasChangeIgnored GasChangeReason = 0xFF diff --git a/core/txpool/validation.go b/core/txpool/validation.go index b53e3ee2bf..27cbb84ad1 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -132,12 +132,12 @@ 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(), from, tx.To(), value, rules, params.CostPerStateByte) + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules) if err != nil { return err } - if tx.Gas() < intrGas.RegularGas { - return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas) + if tx.Gas() < intrGas { + return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas) } // Ensure the transaction can cover floor data gas. if rules.IsPrague { @@ -152,8 +152,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types } // In Amsterdam, the transaction gas limit is allowed to exceed // params.MaxTxGas, but the calldata floor cost is capped by it. - if rules.IsAmsterdam && max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas { - return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas) + if rules.IsAmsterdam && max(intrGas, floorDataGas) > params.MaxTxGas { + return fmt.Errorf("%w: intrinsic cost %v, floor: %v", core.ErrFloorDataGas, intrGas, floorDataGas) } } // Ensure the gasprice is high enough to cover the requirement of the calling pool diff --git a/core/vm/evm.go b/core/vm/evm.go index 15609a0205..72d22f10e8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -473,20 +473,58 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b return ret, exitGas, err } -// create creates a new contract using code as deployment code. -func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, creation bool, err error) { - // Depth check execution. Fail if we're trying to execute above the - // limit. +// createFramePreCheck the precondition before executing the contract deployment, +// halts the create frame if fails with any check below. +func (evm *EVM) createFramePreCheck(caller common.Address, value *uint256.Int) error { var nonce uint64 if evm.depth > int(params.CallCreateDepth) { - err = ErrDepth - } else if !evm.Context.CanTransfer(evm.StateDB, caller, value) { - err = ErrInsufficientBalance - } else { - nonce = evm.StateDB.GetNonce(caller) - if nonce+1 < nonce { - err = ErrNonceUintOverflow - } + return ErrDepth + } + if !evm.Context.CanTransfer(evm.StateDB, caller, value) { + return ErrInsufficientBalance + } + nonce = evm.StateDB.GetNonce(caller) + if nonce+1 < nonce { + return ErrNonceUintOverflow + } + return nil +} + +// chargeAccountCreation runs the create-frame precheck and charges the +// account-creation state gas since Amsterdam, before the 63/64ths split. +// +// The charge only applies if the destination is empty, skipping pre-funded +// deployment destinations. Note, a destination colliding on storage alone +// (zero nonce, zero balance, empty code) is still empty and is charged. +// +// If halt is true, the caller must terminate with the returned error: +// - a failed precheck halts the create frame only and parent frame continues, +// - an insufficient charge halts the parent frame with ErrOutOfGas. +func (evm *EVM) chargeAccountCreation(scope *ScopeContext, contractAddr common.Address, value *uint256.Int) (charged, halt bool, err error) { + if !evm.chainRules.IsAmsterdam { + return false, false, nil + } + if err := evm.createFramePreCheck(scope.Contract.Address(), value); err != nil { + scope.Stack.get().Clear() + evm.returnData = nil + return false, true, nil + } + if !evm.StateDB.Empty(contractAddr) { + return false, false, nil + } + cost := params.AccountCreationSize * evm.Context.CostPerStateByte + if !scope.Contract.chargeState(cost, evm.Config.Tracer, tracing.GasChangeAccountCreation) { + return false, true, ErrOutOfGas + } + return true, false, nil +} + +// create creates a new contract using code as deployment code. +func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, creation bool, err error) { + // Since Amsterdam, the precheck has been folded into the parent frame + // due to account-creation determination, so skip the duplicate check here. + if !evm.chainRules.IsAmsterdam { + evm.createFramePreCheck(caller, value) } if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) @@ -498,7 +536,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value return nil, common.Address{}, gas, false, err } // Increment the caller's nonce after passing all validations - evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) + evm.StateDB.SetNonce(caller, evm.StateDB.GetNonce(caller)+1, tracing.NonceChangeContractCreator) // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { @@ -540,8 +578,11 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value snapshot := evm.StateDB.Snapshot() if !evm.StateDB.Exist(address) { evm.StateDB.CreateAccount(address) - creation = true } + // Explicitly check the creation with EIP-161-emptiness, for pre-funded + // destination (non-zero balance) the creation is skipped. + creation = evm.StateDB.Empty(address) + // CreateContract means that regardless of whether the account previously existed // in the state trie or not, it _now_ becomes created as a _contract_ account. // This is performed _prior_ to executing the initcode, since the initcode diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 2d07b462a6..4f5d4979fd 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -560,12 +560,10 @@ func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m words := (size + 31) / 32 wordGas := params.InitCodeWordGas * words - // Unconditionally pre-charge the account creation and refunds if the creation - // doesn't happen after the create-frame. - return GasCosts{ - RegularGas: gas + wordGas, - StateGas: params.AccountCreationSize * evm.Context.CostPerStateByte, - }, nil + // The account-creation state gas is not part of the opcode cost: it is + // charged conditionally at the destination access, in the creating frame, + // right before the 63/64ths split (see opCreate). + return GasCosts{RegularGas: gas + wordGas}, nil } func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { @@ -590,12 +588,10 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, // (for address hashing). wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words - // Unconditionally pre-charge the account creation and refunds if the creation - // doesn't happen after the create-frame. - return GasCosts{ - RegularGas: gas + wordGas, - StateGas: params.AccountCreationSize * evm.Context.CostPerStateByte, - }, nil + // The account-creation state gas is not part of the opcode cost: it is + // charged conditionally at the destination access, in the creating frame, + // right before the 63/64ths split (see opCreate2). + return GasCosts{RegularGas: gas + wordGas}, nil } // regularGasCall8038 is the intrinsic regular-gas calculator for CALL in @@ -635,19 +631,13 @@ func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error transfersValue = !stack.back(2).IsZero() address = common.Address(stack.back(1).Bytes20()) ) - // TODO(rjl, marius), can EIP8037 implicitly means the EIP158 is also activated? - // It's technically possible to skip the EIP158 but very unlikely in practice. - if evm.chainRules.IsEIP158 { - // Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist - // in the current state yet still be considered non-existent by EIP-161 if its - // nonce, balance, and code are all zero. Such accounts can appear temporarily - // during execution (e.g. via SELFDESTRUCT) and are removed at tx end. - // - // Funding such an account makes it permanent state growth and must be charged. - if transfersValue && evm.StateDB.Empty(address) { - gas += params.AccountCreationSize * evm.Context.CostPerStateByte - } - } else if !evm.StateDB.Exist(address) { + // Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist + // in the current state yet still be considered non-existent by EIP-161 if its + // nonce, balance, and code are all zero. Such accounts can appear temporarily + // during execution (e.g. via SELFDESTRUCT) and are removed at tx end. + // + // Funding such an account makes it permanent state growth and must be charged. + if transfersValue && evm.StateDB.Empty(address) { gas += params.AccountCreationSize * evm.Context.CostPerStateByte } return gas, nil diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index 24ab156afb..220e7d650f 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -48,18 +48,6 @@ func (g GasCosts) String() string { // - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross // consumption. UsedStateGas is signed so it can be decremented by inline // state-gas refunds (e.g., SSTORE 0->A->0). -// -// The same struct serves three roles: -// -// - During execution: Charge / ChargeRegular / ChargeState / RefundState -// and RefundRegular mutate the running balance and the usage accumulators -// in lockstep. -// -// - At frame exit: ExitSuccess / ExitRevert / ExitHalt produce a new -// GasBudget in "leftover" form that packages the result for the caller. -// -// - At absorption: the caller's Absorb method merges the child's leftover -// budget into its own running budget. type GasBudget struct { RegularGas uint64 // remaining regular-gas balance (or leftover for caller to absorb) StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb) @@ -78,9 +66,7 @@ func NewGasBudget(regular, state uint64) GasBudget { return GasBudget{RegularGas: regular, StateGas: state} } -// Used returns the total scalar gas consumed relative to an initial budget -// (= (initial.regular + initial.state) − (current.regular + current.state)). -// This is the payment scalar (EIP-8037's tx_gas_used_before_refund). +// Used returns the total scalar gas consumed relative to an initial budget. func (g GasBudget) Used(initial GasBudget) uint64 { return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas) } @@ -91,16 +77,16 @@ func (g GasBudget) String() string { } // Charge deducts a combined regular+state cost from the running balance and -// updates the usage accumulators. State-gas in excess of the reservoir spills -// into regular_gas. +// updates the usage accumulators. func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) { prior := *g ok := g.charge(cost) return prior, ok } -// chargeRegularOnly deducts a regular-only cost. -func (g *GasBudget) chargeRegularOnly(r uint64) bool { +// ChargeRegularOnly deducts a regular-only cost. It's always preferred for +// performance consideration if the opcode doesn't have any state cost. +func (g *GasBudget) ChargeRegularOnly(r uint64) bool { if g.RegularGas < r { return false } @@ -110,9 +96,7 @@ func (g *GasBudget) chargeRegularOnly(r uint64) bool { } // CanAfford reports whether the running budget can cover the given cost vector -// without going out of gas. The regular cost must fit in the regular balance, -// and any state gas in excess of the reservoir must be coverable by the -// remaining regular gas (the spillover), mirroring charge without mutating. +// without going out of gas. func (g GasBudget) CanAfford(cost GasCosts) bool { if g.RegularGas < cost.RegularGas { return false @@ -162,8 +146,7 @@ func (g *GasBudget) ChargeRegular(r uint64) (GasBudget, bool) { return g.Charge(GasCosts{RegularGas: r}) } -// ChargeState is a convenience that deducts a state-only cost (spills to -// regular when the reservoir is exhausted). Returns false on OOG. +// ChargeState is a convenience that deducts a state-only cost. func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) { return g.Charge(GasCosts{StateGas: s}) } @@ -174,24 +157,10 @@ func (g *GasBudget) IsZero() bool { } // RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0). -// -// Per EIP-8037, the refund repays the regular gas previously borrowed for -// state-gas spillover (tracked by Spilled) before crediting the -// reservoir: it is returned to RegularGas up to the outstanding borrowed -// amount, and only the remainder tops up StateGas. -// -// The signed usage counter is decremented by the full refund regardless of the -// split, preserving the per-frame invariant: -// -// StateGas + UsedStateGas == initialStateGas + Spilled -// -// which the revert and halt paths rely on for the correct gross refund. func (g *GasBudget) RefundState(s uint64) { repay := min(s, g.Spilled) g.RegularGas += repay g.Spilled -= repay - - // Whatever is left tops up the reservoir. g.StateGas += s - repay g.UsedStateGas -= int64(s) } @@ -206,14 +175,6 @@ func (g *GasBudget) DrainRegular() { // the parent's running budget and returns the initial GasBudget for a child // frame. The parent's UsedRegularGas is bumped by the forwarded amount so // that the absorb-on-return path correctly reclaims the unused portion. -// -// Used by frame boundaries where the regular forward has NOT been pre- -// deducted: tx-level dispatch (state_transition) and CREATE / CREATE2. The -// CALL family pre-deducts the forward via the dynamic gas table for tracer- -// reporting reasons and therefore constructs its child budget directly. -// -// Caller must ensure `regular` does not exceed the running balance and -// apply any EIP-150 1/64 retention before calling Forward. func (g *GasBudget) Forward(regular uint64) GasBudget { g.RegularGas -= regular g.UsedRegularGas += regular @@ -239,19 +200,15 @@ func (g *GasBudget) ForwardAll() GasBudget { // absorb to update its own state. // ============================================================================ -// ExitSuccess produces the leftover form for a successful frame. Inline -// state-gas refunds have already been folded into StateGas / UsedStateGas -// during execution; the running budget IS the exit budget on success. +// ExitSuccess produces the leftover form for a successful frame. func (g GasBudget) ExitSuccess() GasBudget { return g } // ExitRevert produces the leftover for a REVERT exit. The frame's state -// changes are discarded, so all state gas it charged is refilled to its origin -// (EIP-8037): up to Spilled is returned to RegularGas (the regular -// gas it borrowed), and the remainder restores the reservoir. Because the -// borrowed regular gas is repaid first, the reservoir is made whole back to its -// start-of-frame value. +// changes are discarded, so all state gas it charged is refilled with LIFO +// mechanism: up to Spilled is returned to RegularGas (the regular gas it +// borrowed), and the remainder restores the reservoir. func (g GasBudget) ExitRevert() GasBudget { reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled) if reservoir < 0 { @@ -270,10 +227,10 @@ func (g GasBudget) ExitRevert() GasBudget { } // ExitHalt produces the leftover for an exceptional halt. As with a revert, the -// frame's state changes are rolled back and its state gas is refilled to origin -// (EIP-8037); the difference is that the frame's gas_left is consumed rather +// frame's state changes are rolled back and its state gas is refilled with LIFO +// mechanism. The difference is that the frame's regular gas is consumed rather // than returned. The portion refilled to RegularGas is therefore burned along -// with the rest of gas_left, leaving only the reservoir portion to survive, +// with the rest of regular gas, leaving only the reservoir portion to survive, // which equals the reservoir's value at the start of the frame. func (g GasBudget) ExitHalt() GasBudget { reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 328623848e..7b7ea48774 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -635,7 +635,12 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { value = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) + contractAddr = crypto.CreateAddress(scope.Contract.Address(), evm.StateDB.GetNonce(scope.Contract.Address())) ) + creationCharged, halt, err := evm.chargeAccountCreation(scope, contractAddr, &value) + if halt { + return nil, err + } // Apply EIP-150 to the regular gas left after the state charge. forward := scope.Contract.Gas.RegularGas if evm.chainRules.IsEIP150 { @@ -646,7 +651,8 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { stackvalue := size child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation) - res, addr, result, creation, suberr := evm.Create(scope.Contract.Address(), input, child, &value) + res, addr, result, _, suberr := evm.create(scope.Contract.Address(), input, child, &value, contractAddr, CREATE) + // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -663,8 +669,9 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Refund the leftover gas back to current frame scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) - // Refund the state gas of account-creation if creation doesn't happen - if evm.GetRules().IsAmsterdam && !creation { + // Refill the account-creation charge if the create frame failed (reverted, + // halted exceptionally, or collided); a successful creation consumes it. + if creationCharged && suberr != nil { scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation) } if suberr == ErrExecutionReverted { @@ -681,7 +688,13 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { offset, size = scope.Stack.pop(), scope.Stack.pop() salt = scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) + inithash = crypto.Keccak256Hash(input) + contractAddr = crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash[:]) ) + creationCharged, halt, err := evm.chargeAccountCreation(scope, contractAddr, &endowment) + if halt { + return nil, err + } // Apply EIP-150 to the regular gas left after the state charge. forward := scope.Contract.Gas.RegularGas forward -= forward / 64 @@ -689,7 +702,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // reuse size int for stackvalue stackvalue := size child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2) - res, addr, result, creation, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt) + res, addr, result, _, suberr := evm.create(scope.Contract.Address(), input, child, &endowment, contractAddr, CREATE2) // Push item on the stack based on the returned error. if suberr != nil { stackvalue.Clear() @@ -701,8 +714,9 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Refund the leftover gas back to current frame scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) - // Refund the state gas of account-creation if creation doesn't happen - if evm.GetRules().IsAmsterdam && !creation { + // Refill the account-creation charge if the create frame failed (reverted, + // halted exceptionally, or collided); a successful creation consumes it. + if creationCharged && suberr != nil { scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation) } if suberr == ErrExecutionReverted { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index df69c3d1fd..c2dfe3769c 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -192,7 +192,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } // for tracing: this gas consumption event is emitted below in the debug section. - if !contract.Gas.chargeRegularOnly(cost) { + if !contract.Gas.ChargeRegularOnly(cost) { return nil, ErrOutOfGas } @@ -223,7 +223,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } if dynamicCost.StateGas == 0 { - if !contract.Gas.chargeRegularOnly(dynamicCost.RegularGas) { + if !contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas) { return nil, ErrOutOfGas } } else if !contract.Gas.charge(dynamicCost) { diff --git a/params/protocol_params.go b/params/protocol_params.go index 6d2e7e6ef1..7928972a51 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -101,14 +101,12 @@ const ( // defined in EIP-8037 as the sum of: // // - Calldata cost for the authorization tuple - // - ECDSA recovery of the authority address (per EIP-7904) + // - ECDSA recovery of the authority address // - Cold authority access (COLD_ACCOUNT_ACCESS) // - Warm writes to the authority account RegularPerAuthBaseCost uint64 = 7816 - // EIP-2780: resource-based intrinsic transaction gas. The access primitives - // it references (COLD_ACCOUNT_ACCESS, WARM_ACCESS, CREATE_ACCESS) are the - // EIP-8038 parameters defined below. + // EIP-2780: resource-based intrinsic transaction gas. TxBaseCost2780 uint64 = 12000 TxValueCost2780 uint64 = 4244 TransferLogCost2780 uint64 = 1756 diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 010c31324b..7f021633bc 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -86,11 +86,11 @@ func (tt *TransactionTest) Run() error { if overflow { return sender, hash, 0, errors.New("value exceeds 256 bits") } - cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules, params.CostPerStateByte) + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules) if err != nil { return } - requiredGas = cost.RegularGas + requiredGas = cost if requiredGas > tx.Gas() { return sender, hash, 0, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas) }