diff --git a/core/state_transition.go b/core/state_transition.go index 3b04c63014..09a22a193b 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -230,12 +230,18 @@ func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) tokenCost = params.TxCostFloorPerToken } + // The floor cost is anchored to the transaction base cost. EIP-2780 + // (Amsterdam) replaces the legacy 21000 base with TX_BASE (12000). + floorBase := params.TxGas + if rules.IsAmsterdam { + floorBase = params.TxBaseCost2780 + } // Check for overflow - if (math.MaxUint64-params.TxGas)/tokenCost < tokens { + if (math.MaxUint64-floorBase)/tokenCost < tokens { return 0, ErrGasUintOverflow } // Minimum gas required for a transaction based on its data tokens (EIP-7623). - return params.TxGas + tokens*tokenCost, nil + return floorBase + tokens*tokenCost, nil } // toWordSize returns the ceiled word size required for init code payment calculation. @@ -1015,6 +1021,10 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se // (account_exists), since no new account is created. if st.state.Exist(authority) { st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte) + // EIP-8038: the worst-case ACCOUNT_WRITE charged per authorization in + // the intrinsic cost is refunded to the regular refund counter when + // the authority account already exists (no new account is created). + st.state.AddRefund(params.AccountWriteAmsterdam) } // - AUTH_BASE is refunded when no new delegation-indicator bytes are // written: either the authority already carries code/delegation diff --git a/core/vm/eips.go b/core/vm/eips.go index 82ccf33df0..40783af346 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -600,4 +600,21 @@ func enable8037(jt *JumpTable) { jt[CREATE2].dynamicGas = gasCreate2Eip8037 jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037 jt[SSTORE].dynamicGas = gasSStore8037 + // EIP-8038: EXTCODESIZE charges an additional warm-access "code reading" + // cost. EXTCODECOPY's extra cost is handled inline in gasExtCodeCopyEIP2929; + // EXTCODESIZE shares gasEip2929AccountCheck with BALANCE/EXTCODEHASH (which + // do not get the extra cost), so it needs a dedicated calculator. + jt[EXTCODESIZE].dynamicGas = gasExtCodeSize8037 +} + +// gasExtCodeSize8037 is the EXTCODESIZE gas calculator for Amsterdam. It adds +// the EIP-8038 warm-access "code reading" cost on top of the account-access +// cost computed by gasEip2929AccountCheck. +func gasExtCodeSize8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { + cost, err := gasEip2929AccountCheck(evm, contract, stack, mem, memorySize) + if err != nil { + return GasCosts{}, err + } + cost.RegularGas += params.WarmStorageReadCostEIP2929 + return cost, nil } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 7fcfe4f595..1be270e8e0 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -492,6 +492,10 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m var transferGas uint64 if transfersValue && !evm.chainRules.IsEIP4762 { transferGas = params.CallValueTransferGas + if evm.chainRules.IsAmsterdam { + // EIP-8038: CALL_VALUE = ACCOUNT_WRITE + CALL_STIPEND. + transferGas = params.CallValueTransferAmsterdam + } } var overflow bool if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow { @@ -546,7 +550,12 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor overflow bool ) if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas + transferGas := params.CallValueTransferGas + if evm.chainRules.IsAmsterdam { + // EIP-8038: CALL_VALUE = ACCOUNT_WRITE + CALL_STIPEND. + transferGas = params.CallValueTransferAmsterdam + } + gas += transferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow @@ -606,7 +615,7 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory 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 + gas.RegularGas = params.ColdAccountAccessAmsterdam } // Check we have enough regular gas before we add the address to the BAL if contract.Gas.RegularGas < gas.RegularGas { @@ -620,78 +629,78 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory // Funding such an account makes it permanent state growth and must be charged. if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte + // EIP-8038: positive balance sent to an empty account also charges the + // regular ACCOUNT_WRITE cost. + gas.RegularGas += params.AccountWriteAmsterdam } return gas, nil } +// gasSStore8037 is the SSTORE gas calculator for Amsterdam (EIP-8037 + +// EIP-8038). It mirrors amsterdam/vm/instructions/storage.py::sstore: +// +// - a cold/warm access cost is always charged (regular); +// - a STORAGE_WRITE cost is charged once, on the first change to the slot in +// the transaction (regular); +// - creating a slot from zero charges STORAGE_SET state gas, refunded to the +// reservoir if the slot is later restored to zero in the same tx; +// - clearing an originally non-zero slot credits/reverses REFUND_STORAGE_CLEAR +// and restoring a changed slot refunds the STORAGE_WRITE, both via the +// (gas_used/5-capped) refund counter. 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 { + // Stipend sentry: require strictly more than the call stipend (spec + // check_gas(CALL_STIPEND + 1)). + if contract.Gas.RegularGas <= params.CallStipend { 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) + newValue = common.Hash(y.Bytes32()) + stateSetGas = params.StorageCreationSize * evm.Context.CostPerStateByte cost GasCosts ) - // Check slot presence in the access list + // Access cost: cold or warm, always charged. 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 + // If the caller cannot afford the cost, this change will be rolled back. evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + cost.RegularGas += params.ColdStorageAccessAmsterdam + } else { + cost.RegularGas += params.WarmStorageReadCostEIP2929 } - 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 + // Write cost: charged on the first change to the slot this transaction. + if original == current && current != newValue { + cost.RegularGas += params.StorageWriteAmsterdam } - if original == current { - if original == (common.Hash{}) { // create slot (2.1.1) - return GasCosts{ - RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929, - StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte, - }, nil + // Refund counter (regular). + if current != newValue { + if original != (common.Hash{}) && current != (common.Hash{}) && newValue == (common.Hash{}) { + // Storage cleared for the first time in the transaction. + evm.StateDB.AddRefund(params.SstoreClearsRefundAmsterdam) } - if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529) + if original != (common.Hash{}) && current == (common.Hash{}) { + // A refund issued earlier this tx is reversed. + evm.StateDB.SubRefund(params.SstoreClearsRefundAmsterdam) } - // 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 == newValue { + // Slot restored to its original value: refund the STORAGE_WRITE + // charged on the first-time change earlier this transaction. + evm.StateDB.AddRefund(params.StorageWriteAmsterdam) } } - if original == value { - if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - // EIP-8037 point (2): refund state gas directly to the reservoir - // at the SSTORE restoration point (0→x→0 in same tx); not to the - // refund counter, which is capped at gas_used/5. - contract.Gas.RefundState(params.StorageCreationSize * evm.Context.CostPerStateByte) - - // Regular portion of the refund still goes through the refund counter. - evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929) - } else { // reset to original existing slot (2.2.2.2) - // EIP 2200 Original clause: - // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) - // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) - // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST - // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST - evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) - } + // State gas (reservoir). + if original == current && current != newValue && original == (common.Hash{}) { + // Slot created from zero: charge STORAGE_SET state gas. + cost.StateGas = stateSetGas } - // EIP-2200 original clause: - //return params.SloadGasEIP2200, nil // dirty update (2.2) - return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2) + if current != newValue && original == newValue && original == (common.Hash{}) { + // Slot set then cleared in the same tx: refund the state gas directly + // to the reservoir (not the gas_used/5-capped refund counter). + contract.Gas.RefundState(stateSetGas) + } + return cost, nil } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 2206fb95fa..0a813e52f8 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -106,7 +106,12 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) - return GasCosts{RegularGas: params.ColdSloadCostEIP2929}, nil + coldCost := params.ColdSloadCostEIP2929 + if evm.chainRules.IsAmsterdam { + // EIP-8038: cold storage access raised to 3000. + coldCost = params.ColdStorageAccessAmsterdam + } + return GasCosts{RegularGas: coldCost}, nil } return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil } @@ -125,16 +130,24 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo gas := gasCost.RegularGas addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list + coldCost := params.ColdAccountAccessCostEIP2929 + // EIP-8038: in Amsterdam cold account access is 3000 and EXTCODECOPY also + // charges an extra warm-access "code reading" cost. + var codeReadingCost uint64 + if evm.chainRules.IsAmsterdam { + coldCost = params.ColdAccountAccessAmsterdam + codeReadingCost = params.WarmStorageReadCostEIP2929 + } if !evm.StateDB.AddressInAccessList(addr) { evm.StateDB.AddAddressToAccessList(addr) var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas - if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + if gas, overflow = math.SafeAdd(gas, coldCost-params.WarmStorageReadCostEIP2929); overflow { return GasCosts{}, ErrGasUintOverflow } - return GasCosts{RegularGas: gas}, nil + return GasCosts{RegularGas: gas + codeReadingCost}, nil } - return GasCosts{RegularGas: gas}, nil + return GasCosts{RegularGas: gas + codeReadingCost}, nil } // gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. @@ -150,8 +163,13 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem if !evm.StateDB.AddressInAccessList(addr) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(addr) + coldCost := params.ColdAccountAccessCostEIP2929 + if evm.chainRules.IsAmsterdam { + // EIP-8038: cold account access raised to 3000. + coldCost = params.ColdAccountAccessAmsterdam + } // The warm storage read cost is already charged as constantGas - return GasCosts{RegularGas: params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929}, nil + return GasCosts{RegularGas: coldCost - params.WarmStorageReadCostEIP2929}, nil } return GasCosts{}, nil } @@ -282,7 +300,12 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { eip2929Cost uint64 eip7702Cost uint64 addr = common.Address(stack.back(1).Bytes20()) + // EIP-8038: cold account access raised to 3000 in Amsterdam. + coldCost = params.ColdAccountAccessCostEIP2929 ) + if evm.chainRules.IsAmsterdam { + coldCost = params.ColdAccountAccessAmsterdam + } // Perform EIP-2929 checks (stateless), checking address presence // in the accessList and charge the cold access accordingly. if !evm.StateDB.AddressInAccessList(addr) { @@ -291,7 +314,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { // The WarmStorageReadCostEIP2929 (100) is already deducted in the form // of a constant cost, so the cost to charge for cold access, if any, // is Cold - Warm - eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929 // Charge the remaining difference here already, to correctly calculate // available gas for call @@ -322,7 +345,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { eip7702Cost = params.WarmStorageReadCostEIP2929 } else { evm.StateDB.AddAddressToAccessList(target) - eip7702Cost = params.ColdAccountAccessCostEIP2929 + eip7702Cost = coldCost } if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return GasCosts{}, ErrOutOfGas diff --git a/params/protocol_params.go b/params/protocol_params.go index 7532b0f834..1f7500a356 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -88,7 +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. + CreateGasAmsterdam uint64 = 11000 // Regular gas portion of CREATE in Amsterdam (EIP-8038: ACCOUNT_WRITE + COLD_STORAGE_ACCESS); state gas is charged separately. CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. @@ -101,7 +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 + TxAuthTupleRegularGas uint64 = 15816 // Per auth tuple regular gas in Amsterdam (EIP-8038: ACCOUNT_WRITE 8000 + REGULAR_PER_AUTH_BASE_COST 7816) // EIP-2780: resource-based intrinsic transaction gas. TxBaseCost2780 uint64 = 12000 @@ -110,6 +110,16 @@ const ( TxValueCost2780 uint64 = 4244 TransferLogCost2780 uint64 = 1756 + // EIP-8038: state-access gas cost update (the Amsterdam repricing of the + // regular-gas portion of state-touching operations). These supersede the + // EIP-2929 cold-access costs for the Amsterdam fork only. + ColdAccountAccessAmsterdam uint64 = 3000 // COLD_ACCOUNT_ACCESS + ColdStorageAccessAmsterdam uint64 = 3000 // COLD_STORAGE_ACCESS + StorageWriteAmsterdam uint64 = 10000 // STORAGE_WRITE (regular write cost, charged on first change) + AccountWriteAmsterdam uint64 = 8000 // ACCOUNT_WRITE + CallValueTransferAmsterdam uint64 = 10300 // CALL_VALUE = ACCOUNT_WRITE + CALL_STIPEND + SstoreClearsRefundAmsterdam uint64 = 12480 // REFUND_STORAGE_CLEAR = (STORAGE_WRITE + COLD_STORAGE_ACCESS) * 4800 / 5000 + // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine) @@ -249,10 +259,10 @@ var ( ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd") // EIP-8282 - Builder Execution Requests - BuilderDepositAddress = common.HexToAddress("0x0000000000000000000000000000000000007732") - BuilderDepositCode = common.FromHex("") // TODO (MariusVanDerWijden) add code - BuilderExitAddress = common.HexToAddress("0x0000000000000000000000000000000000007733") - BuilderExitCode = common.FromHex("") // TODO (MariusVanDerWijden) add code + BuilderDepositAddress = common.HexToAddress("0x0000884d2AA32eAa155F59A2f24eFa73D9008282") + BuilderDepositCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe146101065760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023457600182026001905f5b5f82111560695781019083028483029004916001019190604e565b90939004925050503660b814608957366102345734610234575f5260205ff35b8034106102345760383567ffffffffffffffff1680633b9aca001161023457633b9aca00029034031061023457600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b600354600254808203806101001161011d57506101005b5f5b8181146101c3578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360200181600301548152602001816004015481526020019060050154905260010161011f565b91018092146101d557906002556101e0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561020d57505f5b6001546020828201116102225750505f610228565b01602090035b5f555f60015560b8025ff35b5f5ffd") + BuilderExitAddress = common.HexToAddress("0x000014574A74c805590AFF9499fc7A690f008282") + BuilderExitCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd") ) // System log events.