diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index ad89876601..f4b462572c 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -133,7 +133,8 @@ 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.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + gasCostPerStateByte := core.CostPerStateByte(&types.Header{}, chainConfig) + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte) if err != nil { r.Error = err results = append(results, r) diff --git a/core/bench_test.go b/core/bench_test.go index 20d1a7794b..d49062af06 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -89,7 +89,8 @@ 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, false, false, false) + gasCostPerStateByte := CostPerStateByte(gen.header, gen.cm.config) + cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, gasCostPerStateByte) signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go index 1b033151d3..e38c401eda 100644 --- a/core/bintrie_witness_test.go +++ b/core/bintrie_witness_test.go @@ -63,12 +63,12 @@ var ( func TestProcessUBT(t *testing.T) { var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, 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, true, true, true) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0) signer = types.LatestSigner(testUBTChainConfig) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain @@ -199,7 +199,7 @@ func TestProcessParentBlockHash(t *testing.T) { if isUBT { chainConfig = testUBTChainConfig } - vmContext := NewEVMBlockContext(header, nil, new(common.Address)) + vmContext := NewEVMBlockContext(header, &BlockChain{chainConfig: chainConfig}, new(common.Address)) evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) ProcessParentBlockHash(header.ParentHash, evm) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 1a2ee45291..4b5392a087 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3865,7 +3865,8 @@ func TestTransientStorageReset(t *testing.T) { Data: initCode, }) nonce++ - b.AddTxWithVMConfig(tx, vmConfig) + bc := &BlockChain{chainConfig: gspec.Config} + b.AddTxWithVMConfig(bc, tx, vmConfig) tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: nonce, @@ -3873,7 +3874,7 @@ func TestTransientStorageReset(t *testing.T) { Gas: 100000, To: &destAddress, }) - b.AddTxWithVMConfig(tx, vmConfig) + b.AddTxWithVMConfig(bc, tx, vmConfig) nonce++ }) diff --git a/core/chain_makers.go b/core/chain_makers.go index 46cd98de61..ffeea2054c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -163,8 +163,8 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { // AddTxWithVMConfig adds a transaction to the generated block. If no coinbase has // been set, the block's coinbase is set to the zero address. // The evm interpreter can be customized with the provided vm config. -func (b *BlockGen) AddTxWithVMConfig(tx *types.Transaction, config vm.Config) { - b.addTx(nil, config, tx) +func (b *BlockGen) AddTxWithVMConfig(bc *BlockChain, tx *types.Transaction, config vm.Config) { + b.addTx(bc, config, tx) } // GetBalance returns the balance of the given address at the generated block. diff --git a/core/evm.go b/core/evm.go index 818b23bee5..fc81aabb70 100644 --- a/core/evm.go +++ b/core/evm.go @@ -68,21 +68,33 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common } return vm.BlockContext{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: header.Time, - Difficulty: new(big.Int).Set(header.Difficulty), - BaseFee: baseFee, - BlobBaseFee: blobBaseFee, - GasLimit: header.GasLimit, - Random: random, - SlotNum: slotNum, + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, chain), + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: baseFee, + BlobBaseFee: blobBaseFee, + GasLimit: header.GasLimit, + Random: random, + SlotNum: slotNum, + CostPerGasByte: CostPerStateByte(header, chain.Config()), } } +// CostPerStateByte computes the cost per one byte of state creation +// after EIP-8037 +func CostPerStateByte(header *types.Header, config *params.ChainConfig) uint64 { + if config.IsAmsterdam(header.Number, header.Time) { + return 1174 + // TODO (MariusVanDerWijden): for devnet-3 we hardcode the costPerStateByte + //return ((header.GasLimit / 2) * 7200 * 365) / params.TargetStateGrowthPerYear + } + return 0 +} + // NewEVMTxContext creates a new transaction context for a single transaction. func NewEVMTxContext(msg *Message) vm.TxContext { ctx := vm.TxContext{ diff --git a/core/gaspool.go b/core/gaspool.go index 14f5abd93c..7cab36307e 100644 --- a/core/gaspool.go +++ b/core/gaspool.go @@ -27,6 +27,11 @@ type GasPool struct { remaining uint64 initial uint64 cumulativeUsed uint64 + + // EIP-8037: per-dimension cumulative sums for Amsterdam. + // Block gas used = max(cumulativeRegular, cumulativeState). + cumulativeRegular uint64 + cumulativeState uint64 } // NewGasPool initializes the gasPool with the given amount. @@ -68,20 +73,44 @@ func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error { return nil } +// ReturnGasAmsterdam calculates the new remaining gas in the pool after the +// execution of a message. +func (gp *GasPool) ReturnGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error { + gp.cumulativeRegular += txRegular + gp.cumulativeState += txState + gp.cumulativeUsed += receiptGasUsed + + blockUsed := max(gp.cumulativeRegular, gp.cumulativeState) + if gp.initial < blockUsed { + return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)", + ErrGasLimitReached, gp.initial, blockUsed, gp.cumulativeRegular, gp.cumulativeState) + } + // TX inclusion: only the regular dimension is checked when deciding + // whether the next transaction fits. + gp.remaining = gp.initial - gp.cumulativeRegular + return nil +} + // Gas returns the amount of gas remaining in the pool. func (gp *GasPool) Gas() uint64 { return gp.remaining } -// CumulativeUsed returns the amount of cumulative consumed gas (refunded included). +// CumulativeUsed returns the cumulative gas consumed for receipt tracking. +// For Amsterdam blocks, this is the sum of per-tx tx_gas_used_after_refund +// (what users pay), not the 2D block-level metric. func (gp *GasPool) CumulativeUsed() uint64 { return gp.cumulativeUsed } -// Used returns the amount of consumed gas. +// Used returns the amount of consumed gas. For Amsterdam blocks with +// 2D gas accounting (EIP-8037), returns max(sum_regular, sum_state). func (gp *GasPool) Used() uint64 { + if gp.cumulativeRegular > 0 || gp.cumulativeState > 0 { + return max(gp.cumulativeRegular, gp.cumulativeState) + } if gp.initial < gp.remaining { - panic("gas used underflow") + panic(fmt.Sprintf("gas used underflow: %v %v", gp.initial, gp.remaining)) } return gp.initial - gp.remaining } @@ -89,9 +118,11 @@ func (gp *GasPool) Used() uint64 { // Snapshot returns the deep-copied object as the snapshot. func (gp *GasPool) Snapshot() *GasPool { return &GasPool{ - initial: gp.initial, - remaining: gp.remaining, - cumulativeUsed: gp.cumulativeUsed, + initial: gp.initial, + remaining: gp.remaining, + cumulativeUsed: gp.cumulativeUsed, + cumulativeRegular: gp.cumulativeRegular, + cumulativeState: gp.cumulativeState, } } @@ -100,6 +131,8 @@ func (gp *GasPool) Set(other *GasPool) { gp.initial = other.initial gp.remaining = other.remaining gp.cumulativeUsed = other.cumulativeUsed + gp.cumulativeRegular = other.cumulativeRegular + gp.cumulativeState = other.cumulativeState } func (gp *GasPool) String() string { diff --git a/core/state_processor.go b/core/state_processor.go index fda3bf8fe7..31d521311e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -205,8 +205,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b } receipt.TxHash = tx.Hash() - // GasUsed = max(tx_gas_used - gas_refund, calldata_floor_gas_cost), unchanged - // in the Amsterdam fork. + // GasUsed = max(tx_gas_used - gas_refund, calldata_floor_gas_cost) receipt.GasUsed = result.UsedGas if tx.Type() == types.BlobTxType { @@ -261,7 +260,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) - _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) + _, _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560) if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } @@ -288,7 +287,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) - _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) + _, _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560) if err != nil { panic(err) } @@ -327,7 +326,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte } evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(addr) - ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) + ret, _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudgetReg(30_000_000), common.U2560) if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 2700aed505..9f85510269 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -20,6 +20,7 @@ import ( "crypto/ecdsa" "math" "math/big" + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -432,3 +433,109 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr } return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) } + +// TestEIP8037MaxRegularGasValidation tests that transactions with floor data gas +// exceeding MaxTxGas are rejected in Amsterdam. +func TestEIP8037MaxRegularGasValidation(t *testing.T) { + var ( + // Create an Amsterdam-enabled chain config + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + AmsterdamTime: u64(0), + TerminalTotalDifficulty: big.NewInt(0), + Ethash: new(params.EthashConfig), + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + Amsterdam: params.DefaultOsakaBlobConfig, + }, + } + signer = types.LatestSigner(config) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + ) + + // Calculate how much data is needed to exceed MaxTxGas with floor data gas. + // FloorDataGas = TxGas + tokens * TxCostFloorPerToken + // For non-zero bytes: tokens = 4 per byte, so cost = 40 gas per byte + // MaxTxGas = 16,777,216 + // To exceed: tokens * 10 > MaxTxGas - TxGas = 16,756,216 + // For non-zero bytes: bytes * 4 * 10 > 16,756,216 → bytes > 418,905 + dataSize := 420000 // This should exceed MaxTxGas + largeData := make([]byte, dataSize) + for i := range largeData { + largeData[i] = 0xFF // Non-zero bytes have higher token cost + } + + // Verify that floor data gas exceeds MaxTxGas + floorGas, err := FloorDataGas(largeData) + if err != nil { + t.Fatalf("Failed to calculate floor data gas: %v", err) + } + if floorGas <= params.MaxTxGas { + t.Fatalf("Test setup error: floor data gas %d should exceed MaxTxGas %d", floorGas, params.MaxTxGas) + } + t.Logf("Floor data gas: %d, MaxTxGas: %d", floorGas, params.MaxTxGas) + + // Create a transaction with large calldata. The gas limit is set high enough + // to cover the floor data gas, but since floor data gas > MaxTxGas, + // this should be rejected in Amsterdam. + tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ + Nonce: 0, + GasTipCap: big.NewInt(params.InitialBaseFee), + GasFeeCap: big.NewInt(params.InitialBaseFee), + Gas: floorGas + 1000000, // Enough gas to cover floor + state gas + To: &common.Address{}, + Value: big.NewInt(0), + Data: largeData, + }), signer, key1) + + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: config, + GasLimit: 100_000_000, // High block gas limit to not interfere with the test + Alloc: types.GenesisAlloc{ + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{ + Balance: new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)), // Lots of ether + Nonce: 0, + }, + }, + } + blockchain, _ = NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), nil) + ) + defer blockchain.Stop() + + block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), types.Transactions{tx}, config, false) + _, err = blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block with floor data gas > MaxTxGas should have been rejected") + } + // The error should indicate that max regular gas exceeds the limit. + // This validates EIP-8037: even though the tx gas limit can exceed MaxTxGas, + // the regular gas consumption (max of intrinsic and floor) must still fit. + // Without this check, the transaction would be processed but cause + // state inconsistency (showing up as "invalid gas used" error later). + if !strings.Contains(err.Error(), "max regular gas") || !strings.Contains(err.Error(), "exceeds limit") { + t.Fatalf("expected 'max regular gas exceeds limit' error, got: %v", err) + } + t.Logf("Got expected error: %v", err) +} diff --git a/core/state_transition.go b/core/state_transition.go index c3ebffd060..27fe8055b6 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -68,13 +68,20 @@ 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, isHomestead, isEIP2028, isEIP3860 bool) (vm.GasCosts, error) { +// costPerStateByte needs to be set post-Amsterdam. +func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) { // Set the starting gas for the raw transaction - var gas uint64 - if isContractCreation && isHomestead { - gas = params.TxGasContractCreation + var gas vm.GasCosts + if isContractCreation && rules.IsHomestead { + if rules.IsAmsterdam { + // EIP-8037: account creation is state gas; base tx + CREATE overhead is regular gas. + gas.RegularGas = params.TxGas + params.CreateGasAmsterdam + gas.StateGas = params.AccountCreationSize * costPerStateByte + } else { + gas.RegularGas = params.TxGasContractCreation + } } else { - gas = params.TxGas + gas.RegularGas = params.TxGas } dataLen := uint64(len(data)) // Bump the required gas by the amount of transactional data @@ -85,35 +92,40 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set // Make sure we don't exceed uint64 for all data combinations nonZeroGas := params.TxDataNonZeroGasFrontier - if isEIP2028 { + if rules.IsIstanbul { nonZeroGas = params.TxDataNonZeroGasEIP2028 } - if (math.MaxUint64-gas)/nonZeroGas < nz { + if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz { return vm.GasCosts{}, ErrGasUintOverflow } - gas += nz * nonZeroGas + gas.RegularGas += nz * nonZeroGas - if (math.MaxUint64-gas)/params.TxDataZeroGas < z { + if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z { return vm.GasCosts{}, ErrGasUintOverflow } - gas += z * params.TxDataZeroGas + gas.RegularGas += z * params.TxDataZeroGas - if isContractCreation && isEIP3860 { + if isContractCreation && rules.IsShanghai { lenWords := toWordSize(dataLen) - if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { + if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords { return vm.GasCosts{}, ErrGasUintOverflow } - gas += lenWords * params.InitCodeWordGas + gas.RegularGas += lenWords * params.InitCodeWordGas } } if accessList != nil { - gas += uint64(len(accessList)) * params.TxAccessListAddressGas - gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas + gas.RegularGas += uint64(len(accessList)) * params.TxAccessListAddressGas + gas.RegularGas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas } if authList != nil { - gas += uint64(len(authList)) * params.CallNewAccountGas + if rules.IsAmsterdam { + gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas + gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte + } else { + gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas + } } - return vm.GasCosts{RegularGas: gas}, nil + return gas, nil } // FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623). @@ -318,9 +330,14 @@ func (st *stateTransition) buyGas() error { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) } - st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit) - st.initialBudget = st.gasRemaining.Copy() + // After Amsterdam we limit the regular gas to 16k, the data gas to the transaction limit + limit := st.msg.GasLimit + if st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) { + limit = min(st.msg.GasLimit, params.MaxTxGas) + } + st.initialBudget = vm.NewGasBudget(limit, st.msg.GasLimit-limit) + st.gasRemaining = st.initialBudget.Copy() mgvalU256, _ := uint256.FromBig(mgval) st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) return nil @@ -344,9 +361,10 @@ func (st *stateTransition) preCheck() error { } } isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) + isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) if !msg.SkipTransactionChecks { // Verify tx gas limit does not exceed EIP-7825 cap. - if isOsaka && msg.GasLimit > params.MaxTxGas { + if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas { return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) } // Make sure the sender is an EOA @@ -461,18 +479,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { floorDataGas uint64 ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerGasByte) 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 t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { - t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas) - } - // Gas limit suffices for the floor data cost (EIP-7623) + + // Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation. if rules.IsPrague { floorDataGas, err = FloorDataGas(rules, msg.Data) if err != nil { @@ -483,6 +495,29 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } } + if rules.IsAmsterdam { + // EIP-8037: total intrinsic must fit within the transaction gas limit. + if cost.Sum() > msg.GasLimit { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, cost.Sum()) + } + // EIP-8037: the regular gas consumption (intrinsic or floor) must fit within MaxTxGas. + maxRegularGas := max(cost.RegularGas, floorDataGas) + if maxRegularGas > params.MaxTxGas { + return nil, fmt.Errorf("%w: max regular gas %d exceeds limit %d", ErrIntrinsicGas, maxRegularGas, params.MaxTxGas) + } + } + prior, sufficient := st.gasRemaining.Charge(cost) + if !sufficient { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas) + } + if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { + if rules.IsAmsterdam { + t.OnGasChange(msg.GasLimit, st.gasRemaining.RegularGas+st.gasRemaining.StateGas, tracing.GasChangeTxIntrinsicGas) + } else { + t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas) + } + } + if rules.IsEIP4762 { st.evm.AccessEvents.AddTxOrigin(msg.From) @@ -513,11 +548,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) var ( - ret []byte - vmerr error // vm errors do not effect consensus and are therefore not assigned to err + ret []byte + vmerr error // vm errors do not effect consensus and are therefore not assigned to err + authRefund uint64 ) + var execGasUsed vm.GasUsed if contractCreation { - ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value) + ret, _, st.gasRemaining, execGasUsed, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value) } else { // Increment the nonce for the next transaction. st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall) @@ -526,7 +563,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { if msg.SetCodeAuthorizations != nil { for _, auth := range msg.SetCodeAuthorizations { // Note errors are ignored, we simply skip invalid authorizations here. - st.applyAuthorization(&auth) + refund, _ := st.applyAuthorization(rules, &auth) + authRefund += refund } } @@ -540,7 +578,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } // Execute the transaction's call. - ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value) + ret, st.gasRemaining, execGasUsed, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value) } // Record the gas used excluding gas refunds. This value represents the actual @@ -553,28 +591,37 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { if rules.IsPrague { // After EIP-7623: Data-heavy transactions pay the floor gas. if used := st.gasUsed(); used < floorDataGas { - prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used}) + prev := st.gasRemaining.RegularGas + // When the calldata floor exceeds actual gas used, any + // remaining state gas must also be consumed. + targetRemaining := (st.initialBudget.RegularGas + st.initialBudget.StateGas) - floorDataGas + st.gasRemaining.StateGas = 0 + st.gasRemaining.RegularGas = targetRemaining if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { - t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor) + t.OnGasChange(prev, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor) } } if peakGasUsed < floorDataGas { peakGasUsed = floorDataGas } } - // Return gas to the user - st.returnGas() - // Return gas to the gas pool + returned := st.returnGas() if rules.IsAmsterdam { - // Refund is excluded for returning - err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed()) + // EIP-8037: 2D gas accounting for Amsterdam. + // tx_regular = intrinsic_regular + exec_regular_gas_used + // tx_state = intrinsic_state (adjusted) + exec_state_gas_used + // These are tracked independently, not derived from remaining gas. + txState := (cost.StateGas - authRefund) + execGasUsed.StateGas + txRegular := cost.RegularGas + execGasUsed.RegularGas + txRegular = max(txRegular, floorDataGas) + if err := st.gp.ReturnGasAmsterdam(txRegular, txState, st.gasUsed()); err != nil { + return nil, err + } } else { - // Refund is included for returning - err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed()) - } - if err != nil { - return nil, err + if err = st.gp.ReturnGas(returned, st.gasUsed()); err != nil { + return nil, err + } } effectiveTip := msg.GasPrice if rules.IsLondon { @@ -587,8 +634,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // are 0. This avoids a negative effectiveTip being applied to // the coinbase when simulating calls. } else { - fee := new(uint256.Int).SetUint64(st.gasUsed()) + // For Amsterdam, the fee is based on what the user pays (receipt gas used). + feeGas := st.gasUsed() + fee := new(uint256.Int).SetUint64(feeGas) fee.Mul(fee, effectiveTipU256) + + // always read the coinbase account to include it in the BAL (TODO check this is actually part of the spec) + st.state.GetBalance(st.evm.Context.Coinbase) + st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) // add the coinbase to the witness iff the fee is greater than 0 @@ -601,8 +654,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { st.evm.StateDB.AddLog(log) } } + usedGas := st.gasUsed() return &ExecutionResult{ - UsedGas: st.gasUsed(), + UsedGas: usedGas, MaxUsedGas: peakGasUsed, Err: vmerr, ReturnData: ret, @@ -641,30 +695,43 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio } // applyAuthorization applies an EIP-7702 code delegation to the state. -func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error { +func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization) (uint64, error) { authority, err := st.validateAuthorization(auth) if err != nil { - return err + return 0, err } // If the account already exists in state, refund the new account cost // charged in the intrinsic calculation. + var refund uint64 if st.state.Exist(authority) { - st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) + if rules.IsAmsterdam { + // EIP-8037: refund account creation state gas to the reservoir + refund = params.AccountCreationSize * st.evm.Context.CostPerGasByte + st.gasRemaining.StateGas += refund + } else { + st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) + } } + prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority)) + // Update nonce and account code. st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) if auth.Address == (common.Address{}) { // Delegation to zero address means clear. - st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear) - return nil + if isDelegated { + st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear) + } + return refund, nil } - // Otherwise install delegation to auth.Address. - st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization) + // install delegation to auth.Address if the delegation changed + if !isDelegated || auth.Address != prevDelegation { + st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization) + } - return nil + return refund, nil } // calcRefund computes refund counter, capped to a refund quotient. @@ -683,22 +750,25 @@ func (st *stateTransition) calcRefund() vm.GasBudget { if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 { st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds) } - return vm.NewGasBudget(refund) + return vm.NewGasBudgetReg(refund) } // returnGas returns ETH for remaining gas, // exchanged at the original rate. -func (st *stateTransition) returnGas() { - remaining := uint256.NewInt(st.gasRemaining.RegularGas) +func (st *stateTransition) returnGas() uint64 { + gas := st.gasRemaining.RegularGas + st.gasRemaining.StateGas + remaining := uint256.NewInt(gas) remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) - if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 { - st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned) + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && gas > 0 { + st.evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeTxLeftOverReturned) } + return gas } // gasUsed returns the amount of gas used up by the state transition. +// For Amsterdam (2D gas), this includes both regular and state gas consumed. func (st *stateTransition) gasUsed() uint64 { return st.gasRemaining.Used(st.initialBudget) } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 85bf65ac40..4155d9e12d 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -125,13 +125,23 @@ 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, true, rules.IsIstanbul, rules.IsShanghai) + gasCostPerStateByte := core.CostPerStateByte(head, opts.Config) + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte) if err != nil { return err } + if gasCostPerStateByte != 0 { + // We require transactions to pay for 110% of intrinsic gas in order to + // prevent situations where a change in gas limit invalidates a lot + // of transactions in the txpool + if tx.Gas() < (intrGas.RegularGas*10)/9 { + return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas) + } + } if tx.Gas() < intrGas.RegularGas { return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas) } + // Ensure the transaction can cover floor data gas. if rules.IsPrague { floorDataGas, err := core.FloorDataGas(rules, tx.Data()) diff --git a/core/vm/contract.go b/core/vm/contract.go index a55a5dde8b..207d21e4ad 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -42,8 +42,9 @@ type Contract struct { IsDeployment bool IsSystemCall bool - Gas GasBudget - value *uint256.Int + Gas GasBudget + GasUsed GasUsed // EIP-8037: canonical per-frame gas usage accumulator + value *uint256.Int } // NewContract returns a new contract environment for the execution of EVM. @@ -134,18 +135,29 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { logger.OnGasChange(prior, c.Gas.RegularGas, reason) } + c.GasUsed.Add(cost) return true } -// RefundGas refunds the leftover gas budget back to the contract. -func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) { - prior, changed := c.Gas.Refund(refund) - if !changed { +// RefundGas refunds gas to the contract. gasUsed carries the child frame's +// accumulated gas usage metrics (EIP-8037), incorporated on both success and error. +func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBudget, gasUsed GasUsed, logger *tracing.Hooks, reason tracing.GasChangeReason) { + // If the preceding call errored, return the state gas + // to the parent call + if err != nil { + gas.StateGas += gasUsed.StateGas + gasUsed.StateGas = 0 + } + if gas.RegularGas == 0 && gas.StateGas == 0 && gasUsed.StateGas == 0 && gasUsed.RegularGas == 0 { return } if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { - logger.OnGasChange(prior, c.Gas.RegularGas, reason) + logger.OnGasChange(c.Gas.RegularGas, c.Gas.RegularGas+gas.RegularGas, reason) } + c.Gas.RegularGas += gas.RegularGas + c.Gas.StateGas = gas.StateGas + c.GasUsed.StateGas += gasUsed.StateGas + c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas } // Address returns the contracts address diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go index 988cdb91f2..b2cae5bc97 100644 --- a/core/vm/contracts_fuzz_test.go +++ b/core/vm/contracts_fuzz_test.go @@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) { return } inWant := string(input) - RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas), nil, params.Rules{}) + RunPrecompiledContract(nil, p, a, input, NewGasBudgetReg(gas), nil, params.Rules{}) if inHave := string(input); inWant != inHave { t.Errorf("Precompiled %v modified input data", a) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index e7841c8552..37a5bc8053 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}); err != nil { + if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudgetReg(gas), nil, params.Rules{}); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -122,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := test.Gas - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}) + _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudgetReg(gas), nil, params.Rules{}) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -139,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}) + _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudgetReg(gas), nil, params.Rules{}) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { start := time.Now() for bench.Loop() { copy(data, in) - res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas), nil, params.Rules{}) + res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudgetReg(reqGas), nil, params.Rules{}) } elapsed := uint64(time.Since(start)) if elapsed < 1 { diff --git a/core/vm/eips.go b/core/vm/eips.go index 54e5cb0c60..b94d6b7879 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -44,6 +44,7 @@ var activators = map[int]func(*JumpTable){ 7939: enable7939, 8024: enable8024, 7843: enable7843, + 8037: enable8037, } // EnableEIP enables the given EIP on the config. @@ -596,3 +597,16 @@ func enable7843(jt *JumpTable) { maxStack: maxStack(0, 1), } } + +// enable8037 enables the multidimensional-metering as specified in EIP-8037. +func enable8037(jt *JumpTable) { + // EIP-8037: CREATE/CREATE2 constant gas changes from 32000 to 9000 regular; + // the account creation cost moves to state gas (in dynamicGas). + jt[CREATE].constantGas = params.CreateGasAmsterdam + jt[CREATE].dynamicGas = gasCreateEip8037 + jt[CREATE2].constantGas = params.CreateGasAmsterdam + jt[CREATE2].dynamicGas = gasCreate2Eip8037 + jt[CALL].dynamicGas = gasCallEIP8037 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037 + jt[SSTORE].dynamicGas = gasSStore8037 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 59e301c0a7..f7d0d64d77 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -67,6 +67,8 @@ type BlockContext struct { BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price) Random *common.Hash // Provides information for PREVRANDAO SlotNum uint64 // Provides information for SLOTNUM + + CostPerGasByte uint64 // EIP-8037 } // TxContext provides the EVM with information about a transaction. @@ -236,25 +238,26 @@ func isSystemCall(caller common.Address) bool { // parameters. It also handles any necessary value transfer required and takse // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. -func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { +func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { // Capture the tracer start/end events in debug mode if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth, CALL, caller, addr, input, gas.RegularGas, value.ToBig()) - defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) - }(gas.RegularGas) + evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig()) + defer func(startGas GasBudget) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, GasUsed{}, ErrDepth } syscall := isSystemCall(caller) // Fail if we're trying to transfer more than the available balance. if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { - return nil, gas, ErrInsufficientBalance + return nil, gas, GasUsed{}, ErrInsufficientBalance } snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) { @@ -269,13 +272,13 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok { evm.StateDB.RevertToSnapshot(snapshot) gas.Exhaust() - return nil, gas, ErrOutOfGas + return nil, gas, GasUsed{}, ErrOutOfGas } } if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { // Calling a non-existing account, don't do anything. - return nil, gas, nil + return nil, gas, GasUsed{}, nil } evm.StateDB.CreateAccount(addr) } @@ -287,7 +290,13 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g } if isPrecompile { - ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + var stateDB StateDB + if evm.chainRules.IsAmsterdam { + stateDB = evm.StateDB + } + gasBefore := gas.RegularGas + ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + gasUsed.RegularGas += gasBefore - gas.RegularGas } else { // Initialise a new contract and set the code that is to be used by the EVM. code := evm.resolveCode(addr) @@ -300,6 +309,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g contract.SetCallCode(evm.resolveCodeHash(addr), code) ret, err = evm.Run(contract, input, false) gas = contract.Gas + gasUsed = contract.GasUsed } } // When an error was returned by the EVM or when setting the creation code @@ -307,17 +317,16 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + isRevert := err == ErrExecutionReverted + if !isRevert { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } gas.Exhaust() + gasUsed.RegularGas += gas.RegularGas } - // TODO: consider clearing up unused snapshots: - //} else { - // evm.StateDB.DiscardSnapshot(snapshot) } - return ret, gas, err + return ret, gas, gasUsed, err } // CallCode executes the contract associated with the addr with the given input @@ -327,30 +336,33 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g // // CallCode differs from Call in the sense that it executes the given address' // code with the caller as context. -func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { +func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas.RegularGas, value.ToBig()) - defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) - }(gas.RegularGas) + evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig()) + defer func(startGas GasBudget) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, GasUsed{}, ErrDepth } // Fail if we're trying to transfer more than the available balance - // Note although it's noop to transfer X ether to caller itself. But - // if caller doesn't have enough balance, it would be an error to allow - // over-charging itself. So the check here is necessary. if !evm.Context.CanTransfer(evm.StateDB, caller, value) { - return nil, gas, ErrInsufficientBalance + return nil, gas, GasUsed{}, ErrInsufficientBalance } var snapshot = evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + var stateDB StateDB + if evm.chainRules.IsAmsterdam { + stateDB = evm.StateDB + } + gasBefore := gas.RegularGas + ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + gasUsed.RegularGas += gasBefore - gas.RegularGas } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -358,17 +370,20 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.Run(contract, input, false) gas = contract.Gas + gasUsed = contract.GasUsed } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + isRevert := err == ErrExecutionReverted + if !isRevert { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } gas.Exhaust() + gasUsed.RegularGas += gas.RegularGas } } - return ret, gas, err + return ret, gas, gasUsed, err } // DelegateCall executes the contract associated with the addr with the given input @@ -376,121 +391,128 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt // // DelegateCall differs from CallCode in the sense that it executes the given address' // code with the caller as context and the caller is set to the caller of the caller. -func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { +func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { // DELEGATECALL inherits value from parent call - evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas.RegularGas, value.ToBig()) - defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) - }(gas.RegularGas) + evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig()) + defer func(startGas GasBudget) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, GasUsed{}, ErrDepth } var snapshot = evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + var stateDB StateDB + if evm.chainRules.IsAmsterdam { + stateDB = evm.StateDB + } + gasBefore := gas.RegularGas + ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + gasUsed.RegularGas += gasBefore - gas.RegularGas } else { - // Initialise a new contract and make initialise the delegate values - // - // Note: The value refers to the original value from the parent call. contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) ret, err = evm.Run(contract, input, false) gas = contract.Gas + gasUsed = contract.GasUsed } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + isRevert := err == ErrExecutionReverted + if !isRevert { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } gas.Exhaust() + gasUsed.RegularGas += gas.RegularGas } } - return ret, gas, err + return ret, gas, gasUsed, err } // StaticCall executes the contract associated with the addr with the given input // as parameters while disallowing any modifications to the state during the call. // Opcodes that attempt to perform such modifications will result in exceptions // instead of performing the modifications. -func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, err error) { +func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, gasUsed GasUsed, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas.RegularGas, nil) - defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) - }(gas.RegularGas) + evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil) + defer func(startGas GasBudget) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, GasUsed{}, ErrDepth } - // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. - // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced - // after all empty accounts were deleted, so this is not required. However, if we omit this, - // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. - // We could change this, but for now it's left for legacy reasons var snapshot = evm.StateDB.Snapshot() - // We do an AddBalance of zero here, just in order to trigger a touch. - // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, - // but is the correct thing to do and matters on other networks, in tests, and potential - // future scenarios evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + var stateDB StateDB + if evm.chainRules.IsAmsterdam { + stateDB = evm.StateDB + } + gasBefore := gas.RegularGas + ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) + gasUsed.RegularGas += gasBefore - gas.RegularGas } else { - // Initialise a new contract and set the code that is to be used by the EVM. - // The contract is a scoped environment for this execution context only. contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - - // When an error was returned by the EVM or when setting the creation code - // above we revert to the snapshot and consume any gas remaining. Additionally - // when we're in Homestead this also counts for code storage gas errors. ret, err = evm.Run(contract, input, true) gas = contract.Gas + gasUsed = contract.GasUsed } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) - if err != ErrExecutionReverted { + isRevert := err == ErrExecutionReverted + if !isRevert { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } gas.Exhaust() + gasUsed.RegularGas += gas.RegularGas } } - return ret, gas, err + return ret, gas, gasUsed, 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, leftOverGas GasBudget, err error) { - if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth, typ, caller, address, code, gas.RegularGas, value.ToBig()) - defer func(startGas uint64) { - evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) - }(gas.RegularGas) - } +func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasBudget, used GasUsed, err error) { // Depth check execution. Fail if we're trying to execute above the // limit. + var nonce uint64 if evm.depth > int(params.CallCreateDepth) { - return nil, common.Address{}, gas, ErrDepth + 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 + } } - if !evm.Context.CanTransfer(evm.StateDB, caller, value) { - return nil, common.Address{}, gas, ErrInsufficientBalance + + if err == nil { + evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) } - nonce := evm.StateDB.GetNonce(caller) - if nonce+1 < nonce { - return nil, common.Address{}, gas, ErrNonceUintOverflow + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) + defer func(startGas GasBudget) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + if err != nil { + return nil, common.Address{}, gas, GasUsed{}, err } - evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { @@ -498,7 +520,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas}) if !ok { gas.Exhaust() - return nil, common.Address{}, gas, ErrOutOfGas + return nil, common.Address{}, gas, GasUsed{}, ErrOutOfGas } if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractCollisionCheck) @@ -523,12 +545,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) } gas.Exhaust() - return nil, common.Address{}, gas, ErrContractAddressCollision + return nil, common.Address{}, gas, GasCosts{}, ErrContractAddressCollision } // Create a new account on the state only if the object was not present. // It might be possible the contract code is deployed to a pre-existent // account with non-zero balance. snapshot := evm.StateDB.Snapshot() + if !evm.StateDB.Exist(address) { evm.StateDB.CreateAccount(address) } @@ -546,7 +569,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas) if consumed < wanted { gas.Exhaust() - return nil, common.Address{}, gas, ErrOutOfGas + return nil, common.Address{}, gas, GasUsed{}, ErrOutOfGas } prior, _ := gas.Charge(GasCosts{RegularGas: consumed}) if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { @@ -571,7 +594,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) } } - return ret, address, contract.Gas, err + return ret, address, contract.Gas, contract.GasUsed, err } // initNewContract runs a new contract's creation code, performs checks on the @@ -582,27 +605,45 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b return ret, err } - // Check whether the max code size has been exceeded, assign err if the case. - if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { - return ret, err - } - + // Check prefix before gas calculation. // Reject code starting with 0xEF if EIP-3541 is enabled. if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { return ret, ErrInvalidCode } - if !evm.chainRules.IsEIP4762 { - createDataGas := uint64(len(ret)) * params.CreateDataGas - if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + if evm.chainRules.IsAmsterdam { + // Check max code size BEFORE charging gas so over-max code + // does not consume state gas (which would inflate tx_state). + if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { + return ret, err + } + // EIP-8037: Charge regular gas (keccak256 hash) first, then state gas + // (code storage). Regular-before-state prevents reservoir inflation. + regularGas := GasCosts{RegularGas: toWordSize(uint64(len(ret))) * params.Keccak256WordGas} + if !contract.UseGas(regularGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { return ret, ErrCodeStoreOutOfGas } - } else { + stateGas := GasCosts{StateGas: uint64(len(ret)) * evm.Context.CostPerGasByte} + if !contract.UseGas(stateGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + return ret, ErrCodeStoreOutOfGas + } + } else if evm.chainRules.IsEIP4762 { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas) contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if len(ret) > 0 && (consumed < wanted) { return ret, ErrCodeStoreOutOfGas } + if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { + return ret, err + } + } else { + createDataGas := GasCosts{RegularGas: uint64(len(ret)) * params.CreateDataGas} + if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + return ret, ErrCodeStoreOutOfGas + } + if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil { + return ret, err + } } if len(ret) > 0 { @@ -612,7 +653,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } // Create creates a new contract using code as deployment code. -func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) { +func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, gasUsed GasUsed, err error) { contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) return evm.create(caller, code, gas, value, contractAddr, CREATE) } @@ -621,7 +662,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value // // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) { +func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, gasUsed GasUsed, err error) { inithash := crypto.Keccak256Hash(code) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) @@ -659,20 +700,20 @@ func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash { // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } -func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) { +func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas GasBudget, value *big.Int) { tracer := evm.Config.Tracer if tracer.OnEnter != nil { - tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value) + tracer.OnEnter(depth, byte(typ), from, to, input, startGas.RegularGas, value) } if tracer.OnGasChange != nil { - tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance) + tracer.OnGasChange(0, startGas.RegularGas, tracing.GasChangeCallInitialBalance) } } -func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) { +func (evm *EVM) captureEnd(depth int, startGas GasBudget, leftOverGas GasBudget, ret []byte, err error) { tracer := evm.Config.Tracer - if leftOverGas != 0 && tracer.OnGasChange != nil { - tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned) + if leftOverGas.RegularGas != 0 && tracer.OnGasChange != nil { + tracer.OnGasChange(leftOverGas.RegularGas, 0, tracing.GasChangeCallLeftOverReturned) } var reverted bool if err != nil { @@ -682,7 +723,7 @@ func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret [ reverted = false } if tracer.OnExit != nil { - tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) + tracer.OnExit(depth, ret, startGas.RegularGas-leftOverGas.RegularGas, VMErrorFromErr(err), reverted) } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index b3259b2ec7..05425f4db6 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -291,10 +291,19 @@ var ( gasMLoad = pureMemoryGascost gasMStore8 = pureMemoryGascost gasMStore = pureMemoryGascost - gasCreate = pureMemoryGascost ) +func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + return pureMemoryGascost(evm, contract, stack, mem, memorySize) +} + func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -313,6 +322,9 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS } func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -331,7 +343,11 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } return GasCosts{RegularGas: gas}, nil } + func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } gas, err := memoryGasCost(mem, memorySize) if err != nil { return GasCosts{}, err @@ -384,17 +400,17 @@ var ( gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic) ) -func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc { +func makeCallVariantGasCost(intrinsicFunc intrinsicGasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize) if err != nil { return GasCosts{}, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic, stack.Back(0)) if err != nil { return GasCosts{}, err } - gas, overflow := math.SafeAdd(intrinsic.RegularGas, evm.callGasTemp) + gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp) if overflow { return GasCosts{}, ErrGasUintOverflow } @@ -402,19 +418,19 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc { } } -func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 transfersValue = !stack.Back(2).IsZero() address = common.Address(stack.Back(1).Bytes20()) ) if evm.readOnly && transfersValue { - return GasCosts{}, ErrWriteProtection + return 0, ErrWriteProtection } // Stateless check memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } var transferGas uint64 if transfersValue && !evm.chainRules.IsEIP4762 { @@ -422,12 +438,12 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } var overflow bool if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } // Terminate the gas measurement if the leftover gas is not sufficient, // it can effectively prevent accessing the states in the following steps. if contract.Gas.RegularGas < gas { - return GasCosts{}, ErrOutOfGas + return 0, ErrOutOfGas } // Stateful check var stateGas uint64 @@ -439,56 +455,87 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m stateGas += params.CallNewAccountGas } if gas, overflow = math.SafeAdd(gas, stateGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +// gasCallIntrinsic8037 is the intrinsic gas calculator for CALL in Amsterdam. +// It computes memory expansion + value transfer gas but excludes new account +// creation, which is handled as state gas by the wrapper. +func gasCallIntrinsic8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + ) + if evm.readOnly && transfersValue { + return 0, ErrWriteProtection + } memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err + } + var transferGas uint64 + if transfersValue && !evm.chainRules.IsEIP4762 { + transferGas = params.CallValueTransferGas + } + var overflow bool + if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err } var ( - gas uint64 - overflow bool + gas uint64 + overflow bool + transfersValue = !stack.Back(2).IsZero() ) - if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas + if transfersValue { + if !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return GasCosts{}, ErrGasUintOverflow + return 0, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } - return GasCosts{RegularGas: gas}, nil + return gas, nil } -func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { +func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { - return GasCosts{}, err + return 0, err } - return GasCosts{RegularGas: gas}, nil + return gas, nil } func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { if evm.readOnly { return GasCosts{}, ErrWriteProtection } - var gas uint64 // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 - var address = common.Address(stack.Back(0).Bytes20()) + if gas > contract.Gas.RegularGas { + return GasCosts{RegularGas: gas}, nil + } + var address = common.Address(stack.Back(0).Bytes20()) if evm.chainRules.IsEIP158 { // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { @@ -504,3 +551,163 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } return GasCosts{RegularGas: gas}, nil } + +func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return GasCosts{}, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return GasCosts{}, ErrGasUintOverflow + } + if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { + return GasCosts{}, err + } + // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow + words := (size + 31) / 32 + wordGas := params.InitCodeWordGas * words + stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte + return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil +} + +func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return GasCosts{}, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return GasCosts{}, ErrGasUintOverflow + } + if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil { + return GasCosts{}, err + } + // Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow + words := (size + 31) / 32 + // CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas (for address hashing). + wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words + stateGas := params.AccountCreationSize * evm.Context.CostPerGasByte + return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil +} + +// gasCall8037 is the stateful gas calculator for CALL in Amsterdam (EIP-8037). +// It only returns the state-dependent gas (account creation as state gas). +// Memory gas, transfer gas, and callGas are handled by gasCallStateless and +// makeCallVariantGasCall. +func gasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + var ( + gas GasCosts + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) + ) + if evm.chainRules.IsEIP158 { + if transfersValue && evm.StateDB.Empty(address) { + gas.StateGas += params.AccountCreationSize * evm.Context.CostPerGasByte + } + } else if !evm.StateDB.Exist(address) { + gas.StateGas += params.AccountCreationSize * evm.Context.CostPerGasByte + } + return gas, nil +} + +func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + var ( + gas GasCosts + address = common.Address(stack.peek().Bytes20()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas.RegularGas = params.ColdAccountAccessCostEIP2929 + } + // Check we have enough regular gas before we add the address to the BAL + if contract.Gas.RegularGas < gas.RegularGas { + return gas, nil + } + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas.StateGas += params.AccountCreationSize * evm.Context.CostPerGasByte + } + return gas, nil +} + +func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + if evm.readOnly { + return GasCosts{}, ErrWriteProtection + } + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 { + return GasCosts{}, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot) + cost GasCosts + ) + // Check slot presence in the access list + if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929} + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + } + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS + } + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + // EIP-8037: Return both regular and state gas. The interpreter + // charges regular gas before state gas, preventing reservoir + // inflation when the regular charge OOGs. + return GasCosts{ + RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, + StateGas: params.StorageCreationSize * evm.Context.CostPerGasByte, + }, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.StorageCreationSize*evm.Context.CostPerGasByte + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) + } + } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2) +} diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 16ce651a7d..152e9f0998 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -97,8 +97,8 @@ func TestEIP2200(t *testing.T) { Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {}, } evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) - initialGas := NewGasBudget(tt.gaspool) - _, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) + initialGas := NewGasBudgetReg(tt.gaspool) + _, leftOver, _, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) if !errors.Is(err, tt.failure) { t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) } @@ -157,8 +157,8 @@ func TestCreateGas(t *testing.T) { } evm := NewEVM(vmctx, statedb, chainConfig, config) - initialGas := NewGasBudget(uint64(testGas)) - ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) + initialGas := NewGasBudgetReg(uint64(testGas)) + ret, leftOver, _, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) if err != nil { return false } diff --git a/core/vm/gascosts.go b/core/vm/gascosts.go index cc90c54798..ce1fadc443 100644 --- a/core/vm/gascosts.go +++ b/core/vm/gascosts.go @@ -18,6 +18,13 @@ package vm import "fmt" +type GasUsed = GasCosts + +func (g *GasUsed) Add(costs GasCosts) { + g.RegularGas += costs.RegularGas + g.StateGas += costs.StateGas +} + // GasCosts denotes a vector of gas costs in the // multidimensional metering paradigm. It represents the cost // charged by an individual operation. @@ -45,14 +52,18 @@ type GasBudget struct { StateGas uint64 // The state gas reservoir } -// NewGasBudget creates a GasBudget with the given initial regular gas allowance. -func NewGasBudget(gas uint64) GasBudget { +// NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance. +func NewGasBudgetReg(gas uint64) GasBudget { return GasBudget{RegularGas: gas} } -// Used returns the amount of regular gas consumed so far. +func NewGasBudget(regular, state uint64) GasBudget { + return GasBudget{RegularGas: regular, StateGas: state} +} + +// Used returns the total amount of gas consumed so far (regular + state). func (g GasBudget) Used(initial GasBudget) uint64 { - return initial.RegularGas - g.RegularGas + return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas) } // Exhaust sets all remaining gas to zero, preserving the initial amount @@ -72,26 +83,44 @@ func (g GasBudget) String() string { } // CanAfford reports whether the budget has sufficient gas to cover the cost. +// When state gas exceeds the reservoir, the excess spills to regular gas. func (g GasBudget) CanAfford(cost GasCosts) bool { - return g.RegularGas >= cost.RegularGas + if g.RegularGas < cost.RegularGas { + return false + } + if cost.StateGas > g.StateGas { + spillover := cost.StateGas - g.StateGas + if spillover > g.RegularGas-cost.RegularGas { + return false + } + } + return true } // Charge deducts the given gas cost from the budget. It returns the -// pre-charge gas value and false if the budget does not have sufficient -// gas to cover the cost. +// pre-charge regular gas value and false if the budget does not have +// sufficient gas to cover the cost. func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) { prior := g.RegularGas - if prior < cost.RegularGas { + if !g.CanAfford(cost) { return prior, false } g.RegularGas -= cost.RegularGas + if cost.StateGas > g.StateGas { + spillover := cost.StateGas - g.StateGas + g.StateGas = 0 + g.RegularGas -= spillover + } else { + g.StateGas -= cost.StateGas + } return prior, true } -// Refund adds the given gas budget back. It returns the pre-refund gas +// Refund adds the given gas budget back. It returns the pre-refund regular gas // value and whether the budget was actually changed. func (g *GasBudget) Refund(other GasBudget) (uint64, bool) { prior := g.RegularGas g.RegularGas += other.RegularGas - return prior, g.RegularGas != prior + g.StateGas += other.StateGas + return prior, other.RegularGas != 0 || other.StateGas != 0 } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 3311af0d22..5b244bf1b1 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -651,25 +651,23 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - if evm.readOnly { - return nil, ErrWriteProtection - } var ( value = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) - gas = scope.Contract.Gas.RegularGas ) + gas := scope.Contract.Gas if evm.chainRules.IsEIP150 { - gas -= gas / 64 + gas.RegularGas -= gas.RegularGas / 64 } // reuse size int for stackvalue stackvalue := size - scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation) + regularGasUsed := scope.Contract.GasUsed.RegularGas + scope.Contract.UseGas(GasCosts{RegularGas: gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation) - res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas), &value) + res, addr, returnGas, childGasUsed, suberr := evm.Create(scope.Contract.Address(), input, GasBudget{RegularGas: gas.RegularGas, StateGas: scope.Contract.Gas.StateGas}, &value) // 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 @@ -683,7 +681,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } scope.Stack.push(&stackvalue) - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.RefundGas(suberr, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer @@ -694,23 +692,23 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - if evm.readOnly { - return nil, ErrWriteProtection - } var ( endowment = scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop() salt = scope.Stack.pop() input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) - gas = scope.Contract.Gas.RegularGas ) // Apply EIP150 - gas -= gas / 64 - scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + gas := scope.Contract.Gas + gas.RegularGas -= gas.RegularGas / 64 + + regularGasUsed := scope.Contract.GasUsed.RegularGas + scope.Contract.UseGas(GasCosts{RegularGas: gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + // reuse size int for stackvalue stackvalue := size - res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas), + res, addr, returnGas, childGasUsed, suberr := evm.Create2(scope.Contract.Address(), input, GasBudget{RegularGas: gas.RegularGas, StateGas: scope.Contract.Gas.StateGas}, &endowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { @@ -719,7 +717,8 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { stackvalue.SetBytes(addr.Bytes()) } scope.Stack.push(&stackvalue) - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + scope.Contract.RefundGas(suberr, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { evm.returnData = res // set REVERT data to return data buffer @@ -747,7 +746,8 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if !value.IsZero() { gas += params.CallStipend } - ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) + regularGasUsed := scope.Contract.GasUsed.RegularGas - gas // exclude forwarded gas (incl. stipend) + ret, returnGas, childGasUsed, err := evm.Call(scope.Contract.Address(), toAddr, args, GasBudget{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, &value) if err != nil { temp.Clear() @@ -759,7 +759,7 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.RefundGas(err, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil @@ -781,7 +781,8 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { gas += params.CallStipend } - ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) + regularGasUsed := scope.Contract.GasUsed.RegularGas - gas // exclude forwarded gas (incl. stipend) + ret, returnGas, childGasUsed, err := evm.CallCode(scope.Contract.Address(), toAddr, args, GasBudget{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, &value) if err != nil { temp.Clear() } else { @@ -792,7 +793,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.RefundGas(err, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil @@ -810,7 +811,8 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas), scope.Contract.value) + regularGasUsed := scope.Contract.GasUsed.RegularGas - gas // exclude forwarded gas + ret, returnGas, childGasUsed, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, GasBudget{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}, scope.Contract.value) if err != nil { temp.Clear() } else { @@ -821,7 +823,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.RefundGas(err, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil @@ -839,7 +841,8 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas)) + regularGasUsed := scope.Contract.GasUsed.RegularGas - gas // exclude forwarded gas + ret, returnGas, childGasUsed, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, GasBudget{RegularGas: gas, StateGas: scope.Contract.Gas.StateGas}) if err != nil { temp.Clear() } else { @@ -850,7 +853,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + scope.Contract.RefundGas(err, regularGasUsed, returnGas, childGasUsed, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) evm.returnData = ret return ret, nil diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9dc0a0b787..d1d527fd7b 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -196,6 +196,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return nil, ErrOutOfGas } else { contract.Gas.RegularGas -= cost + contract.GasUsed.RegularGas += cost // EIP-8037: track constant gas } // All ops with a dynamic memory usage also has a dynamic gas cost. @@ -225,10 +226,24 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } // for tracing: this gas consumption event is emitted below in the debug section. - if contract.Gas.RegularGas < dynamicCost.RegularGas { - return nil, ErrOutOfGas - } else { + if evm.chainRules.IsAmsterdam && dynamicCost.StateGas > 0 { + // EIP-8037: charge regular gas before state gas. + if contract.Gas.RegularGas < dynamicCost.RegularGas { + return nil, ErrOutOfGas + } contract.Gas.RegularGas -= dynamicCost.RegularGas + contract.GasUsed.RegularGas += dynamicCost.RegularGas + // Then charge state gas. + stateOnly := GasCosts{StateGas: dynamicCost.StateGas} + if _, ok := contract.Gas.Charge(stateOnly); !ok { + return nil, ErrOutOfGas + } + contract.GasUsed.Add(stateOnly) + } else { + if _, ok := contract.Gas.Charge(dynamicCost); !ok { + return nil, ErrOutOfGas + } + contract.GasUsed.Add(dynamicCost) } } diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 69c2316907..d276769b75 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) { timeout := make(chan bool) go func(evm *EVM) { - _, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64), new(uint256.Int)) + _, _, _, err := evm.Call(common.Address{}, address, nil, NewGasBudgetReg(math.MaxUint64), new(uint256.Int)) errChannel <- err }(evm) @@ -85,11 +85,11 @@ func BenchmarkInterpreter(b *testing.B) { value = uint256.NewInt(0) stack = newstack() mem = NewMemory() - contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil) + contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudgetReg(startGas), nil) ) stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123)) - gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) + gasSStoreEIP3529 := makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) for b.Loop() { gasSStoreEIP3529(evm, contract, stack, mem, 1234) } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 82fc43ec13..9ea8349e3a 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -23,8 +23,9 @@ import ( ) type ( - executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error) - gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64 + executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error) + gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64 + intrinsicGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) ) @@ -97,6 +98,7 @@ func newAmsterdamInstructionSet() JumpTable { instructionSet := newOsakaInstructionSet() enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode) enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE) + enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase) return validate(instructionSet) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 313d03819e..06c811955d 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -245,6 +245,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { return GasCosts{}, ErrOutOfGas } } + if gas > contract.Gas.RegularGas { + return GasCosts{RegularGas: gas}, nil + } + // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas @@ -262,6 +266,8 @@ var ( gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic) gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic) gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic) + + innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(gasCallIntrinsic8037, gasCall8037) ) func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { @@ -276,7 +282,17 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem return innerGasCallEIP7702(evm, contract, stack, mem, memorySize) } -func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { +func gasCallEIP8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + // Same write-protection guard as gasCallEIP7702: check before any gas + // charging to avoid incorrectly recording in the access list. + transfersValue := !stack.Back(2).IsZero() + if evm.readOnly && transfersValue { + return GasCosts{}, ErrWriteProtection + } + return innerGasCallEIP8037(evm, contract, stack, mem, memorySize) +} + +func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { var ( eip2929Cost uint64 @@ -312,7 +328,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { // Terminate the gas measurement if the leftover gas is not sufficient, // it can effectively prevent accessing the states in the following steps. // It's an essential safeguard before any stateful check. - if contract.Gas.RegularGas < intrinsicCost.RegularGas { + if contract.Gas.RegularGas < intrinsicCost { return GasCosts{}, ErrOutOfGas } @@ -330,7 +346,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { } // Calculate the gas budget for the nested call. The costs defined by // EIP-2929 and EIP-7702 have already been applied. - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.Back(0)) if err != nil { return GasCosts{}, err } @@ -339,6 +355,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { // part of the dynamic gas. This will ensure it is correctly reported to // tracers. contract.Gas.RegularGas += eip2929Cost + eip7702Cost + // Undo the RegularGasUsed increments from the direct UseGas charges, + // since this gas will be re-charged via the returned cost. + contract.GasUsed.RegularGas -= eip2929Cost + contract.GasUsed.RegularGas -= eip7702Cost // Aggregate the gas costs from all components, including EIP-2929, EIP-7702, // the CALL opcode itself, and the cost incurred by nested calls. @@ -349,7 +369,95 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow { return GasCosts{}, ErrGasUintOverflow } - if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost.RegularGas); overflow { + if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow { + return GasCosts{}, ErrGasUintOverflow + } + if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow { + return GasCosts{}, ErrGasUintOverflow + } + return GasCosts{RegularGas: totalCost}, nil + } +} + +// makeCallVariantGasCallEIP8037 creates a call gas function for Amsterdam (EIP-8037). +// It extends the EIP-7702 pattern with state gas handling and GasUsed tracking. +// intrinsicFunc computes the regular gas (memory + transfer, no new account creation). +// stateGasFunc computes the state gas (new account creation as state gas). +func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + var ( + eip2929Cost uint64 + eip7702Cost uint64 + addr = common.Address(stack.Back(1).Bytes20()) + ) + // EIP-2929 cold access check. + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return GasCosts{}, ErrOutOfGas + } + } + + // Compute intrinsic cost (memory + transfer, no new account creation). + intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize) + if err != nil { + return GasCosts{}, err + } + + // Charge intrinsic cost directly (regular gas). This must happen + // BEFORE state gas to prevent reservoir inflation, and also serves + // as the OOG guard before stateful operations. + if !contract.UseGas(GasCosts{RegularGas: intrinsicCost}, evm.Config.Tracer, tracing.GasChangeCallOpCode) { + return GasCosts{}, ErrOutOfGas + } + + // EIP-7702 delegation check. + if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + if evm.StateDB.AddressInAccessList(target) { + eip7702Cost = params.WarmStorageReadCostEIP2929 + } else { + evm.StateDB.AddAddressToAccessList(target) + eip7702Cost = params.ColdAccountAccessCostEIP2929 + } + if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return GasCosts{}, ErrOutOfGas + } + } + + // Compute and charge state gas (new account creation) AFTER regular gas. + stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize) + if err != nil { + return GasCosts{}, err + } + if stateGas.StateGas > 0 { + stateGasCost := GasCosts{StateGas: stateGas.StateGas} + if _, ok := contract.Gas.Charge(stateGasCost); !ok { + return GasCosts{}, ErrOutOfGas + } + contract.GasUsed.Add(stateGasCost) + } + + // Calculate the gas budget for the nested call (63/64 rule). + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.Back(0)) + if err != nil { + return GasCosts{}, err + } + + // Temporarily undo direct regular charges for tracer reporting. + // The interpreter will charge the returned totalCost. + contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost + contract.GasUsed.RegularGas -= eip2929Cost + eip7702Cost + intrinsicCost + + // Aggregate total cost. + var ( + overflow bool + totalCost uint64 + ) + if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow { + return GasCosts{}, ErrGasUintOverflow + } + if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow { return GasCosts{}, ErrGasUintOverflow } if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow { diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index d57f2c4dcf..c02f04437f 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -88,10 +88,8 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc, withTransferCosts bool) ga } } - contract.Gas.RegularGas -= witnessGas // if the operation fails, adds witness gas to the gas before returning the error gasCost, err := oldCalculator(evm, contract, stack, mem, memorySize) - contract.Gas.RegularGas += witnessGas // restore witness gas so that it can be charged at the callsite gas := gasCost.RegularGas var overflow bool if gas, overflow = math.SafeAdd(gas, witnessGas); overflow { diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 4fafdf3a50..73a52648bd 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -144,11 +144,11 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified) // Call the code with the given configuration. - ret, leftOverGas, err := vmenv.Call( + ret, leftOverGas, _, err := vmenv.Call( cfg.Origin, common.BytesToAddress([]byte("contract")), input, - vm.NewGasBudget(cfg.GasLimit), + vm.NewGasBudgetReg(cfg.GasLimit), uint256.MustFromBig(cfg.Value), ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { @@ -179,10 +179,10 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // - reset transient storage(eip 1153) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) // Call the code with the given configuration. - code, address, leftOverGas, err := vmenv.Create( + code, address, leftOverGas, _, err := vmenv.Create( cfg.Origin, input, - vm.NewGasBudget(cfg.GasLimit), + vm.NewGasBudgetReg(cfg.GasLimit), uint256.MustFromBig(cfg.Value), ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { @@ -213,11 +213,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) // Call the code with the given configuration. - ret, leftOverGas, err := vmenv.Call( + ret, leftOverGas, _, err := vmenv.Call( cfg.Origin, address, input, - vm.NewGasBudget(cfg.GasLimit), + vm.NewGasBudgetReg(cfg.GasLimit), uint256.MustFromBig(cfg.Value), ) if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 6570d73575..5d3e40925f 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo gasLimit uint64 = 31000 startGas uint64 = 10000 value = uint256.NewInt(0) - contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas), nil) + contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudgetReg(startGas), nil) ) evm.SetTxContext(vmctx.txCtx) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index decdf588e1..d58f5fd342 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) { var ( logger = NewStructLogger(nil) evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()}) - contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000), nil) + contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudgetReg(100000), nil) ) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 149e12c5b8..58414b3a87 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1341,6 +1341,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } // Prevent redundant operations if args contain more authorizations than EVM may handle + // TODO change with EIP-8037 maxAuthorizations := uint64(*args.Gas) / params.CallNewAccountGas if uint64(len(args.AuthorizationList)) > maxAuthorizations { return nil, 0, nil, errors.New("insufficient gas to process all authorizations") diff --git a/params/protocol_params.go b/params/protocol_params.go index 9da275c486..1203be3b02 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -88,6 +88,7 @@ const ( LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. Create2Gas uint64 = 32000 // Once per CREATE2 operation + CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately. CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. @@ -100,6 +101,7 @@ const ( TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 + TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037 // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. @@ -186,6 +188,14 @@ const ( HistoryServeWindow = 8191 // Number of blocks to serve historical block hashes for, EIP-2935. MaxBlockSize = 8_388_608 // maximum size of an RLP-encoded block + + TargetStateGrowthPerYear = 100 * 1024 * 1024 * 1024 // 100GB + AccountCreationSize = 112 + StorageCreationSize = 32 + AuthorizationCreationSize = 23 + + // TODO: Add when EIP-7928 is implemented + // GasBlockAccessListItem = 2000 // EIP-7928: gas cost per BAL item for gas limit check ) // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation diff --git a/tests/state_test.go b/tests/state_test.go index 8444d211cf..98e64eab23 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -326,10 +326,10 @@ func runBenchmark(b *testing.B, t *StateTest) { b.StartTimer() start := time.Now() - initialGas := vm.NewGasBudget(msg.GasLimit) + initialGas := vm.NewGasBudgetReg(msg.GasLimit) // Execute the message. - _, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value)) + _, leftOverGas, _, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), uint256.MustFromBig(msg.Value)) if err != nil { b.Error(err) return diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 8b8d0357bf..077db64f39 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -80,8 +80,9 @@ func (tt *TransactionTest) Run() error { if err != nil { return } - // Intrinsic gas - cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + // Intrinsic cost + // TODO (MariusVanDerWijden): correctly set this for post-amsterdam tests. + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, *rules, 0) if err != nil { return }