core: improve EIP-8037 implementations

This commit is contained in:
Gary Rong 2026-05-18 11:19:25 +08:00
parent b86cca23a8
commit 5963fc8408
22 changed files with 333 additions and 288 deletions

View file

@ -133,8 +133,7 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0)
gasCostPerStateByte := core.CostPerStateByte(&types.Header{}, chainConfig)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
if err != nil {
r.Error = err
results = append(results, r)

View file

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

View file

@ -168,7 +168,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
// 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(&BlockChain{chainConfig: b.cm.config}, config, tx)
b.addTx(nil, config, tx)
}
// GetBalance returns the balance of the given address at the generated block.

View file

@ -80,19 +80,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
GasLimit: header.GasLimit,
Random: random,
SlotNum: slotNum,
CostPerStateByte: CostPerStateByte(header, chain.Config()),
CostPerStateByte: params.CostPerStateByte,
}
}
// 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 0
}
return params.CostPerStateByte
}
// NewEVMTxContext creates a new transaction context for a single transaction.
func NewEVMTxContext(msg *Message) vm.TxContext {
ctx := vm.TxContext{

View file

@ -42,9 +42,9 @@ func NewGasPool(amount uint64) *GasPool {
}
}
// SubGas deducts the given amount from the pool if enough gas is
// CheckGasLegacy deducts the given amount from the pool if enough gas is
// available and returns an error otherwise.
func (gp *GasPool) SubGas(amount uint64) error {
func (gp *GasPool) CheckGasLegacy(amount uint64) error {
if gp.remaining < amount {
return ErrGasLimitReached
}
@ -65,31 +65,24 @@ func (gp *GasPool) CheckGasAmsterdam(regularReservation, stateReservation uint64
return nil
}
// ReturnGas adds the refunded gas back to the pool and updates
// ChargeGasLegacy adds the refunded gas back to the pool and updates
// the cumulative gas usage accordingly.
func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
func (gp *GasPool) ChargeGasLegacy(returned uint64, gasUsed uint64) error {
if gp.remaining > math.MaxUint64-returned {
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
}
// The returned gas calculation differs across forks.
//
// - Pre-Amsterdam:
// returned = purchased - remaining (refund included)
//
// - Post-Amsterdam:
// returned = purchased - gasUsed (refund excluded)
// returned = purchased - remaining (refund included)
gp.remaining += returned
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
// regardless of Amsterdam is activated or not.
gp.cumulativeUsed += gasUsed
return nil
}
// ChargeGasAmsterdam calculates the new remaining gas in the pool after the
// execution of a message. Previously we subtracted and re-added gas to the
// gaspool. After Amsterdam we only check if we can include the transaction and charge the
// gaspool at the end.
// gaspool. After Amsterdam we only check if we can include the transaction
// and charge the gaspool at the end.
func (gp *GasPool) ChargeGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error {
gp.cumulativeRegular += txRegular
gp.cumulativeState += txState

View file

@ -683,34 +683,6 @@ func (s *StateDB) IsNewContract(addr common.Address) bool {
return obj.newContract
}
// SameTxSelfDestructs returns the addresses that were both created and
// self-destructed in the current transaction (EIP-6780).
func (s *StateDB) SameTxSelfDestructs() []common.Address {
var out []common.Address
for addr, obj := range s.stateObjects {
if obj.newContract && obj.selfDestructed {
out = append(out, addr)
}
}
return out
}
// NewStorageSlotCount returns the number of storage slots that were written
// to a non-zero value in the current transaction on the given account.
func (s *StateDB) NewStorageSlotCount(addr common.Address) int {
obj, ok := s.stateObjects[addr]
if !ok {
return 0
}
var count int
for _, v := range obj.dirtyStorage {
if v != (common.Hash{}) {
count++
}
}
return count
}
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB {

View file

@ -59,14 +59,6 @@ func (s *hookedStateDB) IsNewContract(addr common.Address) bool {
return s.inner.IsNewContract(addr)
}
func (s *hookedStateDB) SameTxSelfDestructs() []common.Address {
return s.inner.SameTxSelfDestructs()
}
func (s *hookedStateDB) NewStorageSlotCount(addr common.Address) int {
return s.inner.NewStorageSlotCount(addr)
}
func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int {
return s.inner.GetBalance(addr)
}

View file

@ -277,9 +277,15 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
defer tracer.OnSystemCallEnd()
}
}
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
// new storage slots a single system call is expected to write.
//
// This value matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the
// largest per-block bound across the existing system contracts.
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasLimit: 30_000_000 + stateBudget,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
@ -290,7 +296,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, 0), common.U2560)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, stateBudget), common.U2560)
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
@ -306,9 +312,15 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
defer tracer.OnSystemCallEnd()
}
}
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
// new storage slots a single system call is expected to write.
//
// This value matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the
// largest per-block bound across the existing system contracts.
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasLimit: 30_000_000 + stateBudget,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
@ -319,7 +331,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, 0), common.U2560)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, stateBudget), common.U2560)
if err != nil {
panic(err)
}
@ -348,9 +360,15 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
defer tracer.OnSystemCallEnd()
}
}
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
// new storage slots a single system call is expected to write.
//
// This value matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the
// largest per-block bound across the existing system contracts.
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
msg := &Message{
From: params.SystemAddress,
GasLimit: 30_000_000,
GasLimit: 30_000_000 + stateBudget,
GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0),
@ -360,7 +378,7 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil)
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
evm.StateDB.AddAddressToAccessList(addr)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, 0), common.U2560)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, stateBudget), common.U2560)
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@ -375,6 +376,24 @@ func (st *stateTransition) to() common.Address {
return *st.msg.To
}
// buyGas pre-pays gas from the sender's balance and initializes the
// transaction's gas budget. It is invoked at the tail of preCheck.
//
// The balance requirement is the worst-case ETH the tx may need to lock
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
// returns ErrInsufficientFunds. After the check, the sender is actually
// debited `msg.GasLimit × msg.GasPrice` (plus `blobGas × blobBaseFee`
// under Cancun), the cap-vs-tip differential is settled at tx end.
//
// The gas budget is seeded into both `initialBudget` (frozen snapshot
// for tx-end accounting) and `gasRemaining` (live running balance):
//
// - Pre-Amsterdam: one-dimensional regular budget equal to
// `msg.GasLimit`; the state-gas reservoir is zero.
// - Amsterdam+ (EIP-8037): two-dimensional budget. Regular gas is
// capped at `MaxTxGas` (EIP-7825, 16_777_216); any excess from
// `msg.GasLimit` above that cap becomes the state-gas reservoir.
func (st *stateTransition) buyGas() error {
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
@ -428,7 +447,7 @@ func (st *stateTransition) buyGas() error {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
// After Amsterdam we limit the regular gas to 16k, the data gas to the transaction limit
// After Amsterdam we limit the regular gas to 16M, 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)
@ -437,13 +456,32 @@ func (st *stateTransition) buyGas() error {
st.gasRemaining = st.initialBudget.Copy()
if st.evm.Config.Tracer.HasGasHook() {
empty := vm.GasBudget{}
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
}
// Deduct the gas cost from the sender's balance
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
return nil
}
// preCheck performs all pre-execution validation that does not require
// the EVM to run, then ends by calling buyGas to lock in the gas budget.
// It returns a consensus error if any of the following fail:
//
// - Sender nonce matches state and is not at 2^64-1 (EIP-2681).
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam
// (the cap also bounds the regular dimension after Amsterdam, but
// it is enforced there via the two-dimensional budget in buyGas).
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
// EIP-7702 delegation designator.
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
// - Blob fee-cap not below the current blob base fee (Cancun+).
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
// authorization list.
//
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
func (st *stateTransition) preCheck() error {
// Only check transactions that are not fake
msg := st.msg
@ -461,8 +499,10 @@ func (st *stateTransition) preCheck() error {
msg.From.Hex(), stNonce)
}
}
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)
var (
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 !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
@ -539,28 +579,72 @@ func (st *stateTransition) preCheck() error {
return st.buyGas()
}
// execute will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
// - used gas: total gas used (including gas being refunded)
// - returndata: the returned data from evm
// - concrete execution error: various EVM errors which abort the execution, e.g.
// ErrOutOfGas, ErrExecutionReverted
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *stateTransition) execute() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
// 1. the nonce of the message caller is correct
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
// 3. the amount of gas required is available in the block
// 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call
// reserveBlockGasBudget checks if the remaining gas budget in the block pool is
// sufficient for including this transaction.
func (st *stateTransition) reserveBlockGasBudget(rules params.Rules, gasLimit uint64, intrinsicCost vm.GasCosts) error {
var err error
if rules.IsAmsterdam {
// EIP-8037 per-tx 2D block-inclusion check. For each dimension,
// the worst-case contribution is tx.gas minus the other
// dimension's intrinsic (capped at MaxTxGas for the regular
// dimension).
regularReservation := gasLimit
if regularReservation > intrinsicCost.StateGas {
regularReservation -= intrinsicCost.StateGas
} else {
regularReservation = 0
}
regularReservation = min(regularReservation, params.MaxTxGas)
// Check clauses 1-3, buy gas if everything is correct
stateReservation := gasLimit
if stateReservation > intrinsicCost.RegularGas {
stateReservation -= intrinsicCost.RegularGas
} else {
stateReservation = 0
}
err = st.gp.CheckGasAmsterdam(regularReservation, stateReservation)
} else {
err = st.gp.CheckGasLegacy(gasLimit)
}
return err
}
// execute transitions the state by applying the current message and
// returns the EVM execution result with the following fields:
//
// - used gas: total gas used, including gas refunded
// - peak used gas: maximum gas used before applying refunds
// - returndata: data returned by the EVM
// - execution error: EVM-level errors that abort execution, such as
// ErrOutOfGas or ErrExecutionReverted
//
// If a consensus error is encountered, it is returned directly with a
// nil EVM execution result.
func (st *stateTransition) execute() (*ExecutionResult, error) {
// The state-transition pipeline below runs in stages. Each stage may
// abort with a consensus error before the EVM is invoked:
//
// 1. preCheck: nonce, fee-cap, blob and EIP-7702 structural
// checks; ends by calling buyGas to debit the
// sender and seed the two-dimensional gas budget
// (EIP-8037).
// 2. Intrinsic: charges the intrinsic regular + state cost from
// the running budget with overflow detection.
// 3. Block pool: per-dimension inclusion reservation against the
// block gas pool (two-dimensional after Amsterdam,
// EIP-8037).
// 4. Floor pre: EIP-7623 calldata floor must fit in the gas allowance.
// 5. Top-call: run the top-most call, ensuring sender can cover
// the value transfer of the top call frame; init-code
// size respects the cap.
//
// After the EVM has run, the result path applies EIP-8037 state-gas
// refunds, the EIP-3529 regular-refund cap, and the EIP-7623 scalar
// floor (`tx_gas_used = max(tx_gas_used_after_refund, floor)`),
// returns leftover gas to the sender, settles the block pool and
// pays the coinbase tip.
// Stage 1: validate the message and pre-pay gas.
if err := st.preCheck(); err != nil {
return nil, err
}
@ -572,7 +656,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
floorDataGas uint64
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
// Stage 2: charge intrinsic gas (with overflow detection inside
// IntrinsicGas). Under Amsterdam the cost is two-dimensional and
// Charge debits both regular and state in one step.
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
if err != nil {
return nil, err
@ -585,62 +671,33 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
}
// Check if we have enough gas in the block
if rules.IsAmsterdam {
// EIP-8037 per-tx 2D block-inclusion check. For each dimension,
// the worst-case contribution is tx.gas minus the other
// dimension's intrinsic (capped at MaxTxGas for the regular
// dimension).
regularReservation := msg.GasLimit
if regularReservation > cost.StateGas {
regularReservation -= cost.StateGas
} else {
regularReservation = 0
}
regularReservation = min(regularReservation, params.MaxTxGas)
stateReservation := msg.GasLimit
if stateReservation > cost.RegularGas {
stateReservation -= cost.RegularGas
} else {
stateReservation = 0
}
if err := st.gp.CheckGasAmsterdam(regularReservation, stateReservation); err != nil {
return nil, err
}
} else {
if err := st.gp.SubGas(msg.GasLimit); err != nil {
return nil, err
}
// Stage 3: reserve this tx's share of the block gas pool. Under
// Amsterdam this is a two-dimensional per-tx inclusion check; before
// Amsterdam it is a single scalar subtraction.
if err := st.reserveBlockGasBudget(rules, msg.GasLimit, cost); err != nil {
return nil, err
}
// Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation.
// Stage 4: validate the EIP-7623 calldata floor against the gas limit.
// The floor inflates the total gas usage at tx end, so the gas limit
// must be sufficient to cover that.
if rules.IsPrague {
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
if err != nil {
return nil, err
}
// Make sure the transaction has sufficient gas allowance to
// pay the floor cost.
if msg.GasLimit < floorDataGas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, floorDataGas)
}
}
if rules.IsAmsterdam {
if cost.Sum() > msg.GasLimit {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, cost.Sum())
// In Amsterdam, the transaction gas limit is allowed to exceed
// params.MaxTxGas, but the calldata floor cost is capped by it.
if rules.IsAmsterdam {
if max(cost.RegularGas, floorDataGas) > params.MaxTxGas {
return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas)
}
}
// RegularGas must by < 16M
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)
}
}
before, ok := st.gasRemaining.Charge(cost)
if !ok {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
}
if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(before.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
}
if rules.IsEIP4762 {
@ -651,7 +708,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
}
}
// Check clause 6
// Stage 5: top-call affordability, the sender must still be able
// to cover the value transfer of the top frame after gas pre-pay.
value := msg.Value
if value == nil {
value = new(uint256.Int)
@ -689,7 +747,7 @@ 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(rules, &auth)
st.applyAuthorization(rules, &auth)
}
}
@ -706,58 +764,44 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
}
// If this was a failed contract creation, refund the account creation costs.
if rules.IsAmsterdam {
if vmerr != nil {
// If this was a contract creation, refund the account creation costs.
if contractCreation {
refund := params.AccountCreationSize * st.evm.Context.CostPerStateByte
st.gasRemaining.RefundState(refund)
}
} else {
// Compute refunds for selfdestructed slots
cpsb := st.evm.Context.CostPerStateByte
var sdRefund uint64
for _, addr := range st.state.SameTxSelfDestructs() {
r := params.AccountCreationSize * cpsb
r += uint64(st.state.NewStorageSlotCount(addr)) * params.StorageCreationSize * cpsb
r += uint64(st.state.GetCodeSize(addr)) * cpsb
sdRefund += r
}
if sdRefund > 0 {
st.gasRemaining.RefundState(sdRefund)
}
if vmerr != nil && contractCreation {
refund := params.AccountCreationSize * st.evm.Context.CostPerStateByte
st.gasRemaining.RefundState(refund)
}
}
// Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution.
peakGasUsed := st.gasUsed()
peakRegular := st.gasRemaining.UsedRegularGas
// Compute refund counter, capped to a refund quotient.
st.gasRemaining.RefundRegular(st.calcRefund())
if rules.IsPrague {
// After EIP-7623: Data-heavy transactions pay the floor gas.
// We can always guarantee that the initial regular gas allowance
// is sufficient to cover the floor cost.
//
// Pre-Amsterdam, there is a single dimension and gas limit is greater
// than the floor cost.
//
// Since Amsterdam:
// - If GasLimit <= 16M, the state reservoir is initialized to 0,
// and regular_gas_budget >= floor_cost always holds.
// - If GasLimit > 16M, the state reservoir is non-zero, while
// regular_gas_budget == 16M, which is still guaranteed to be
// greater than the floor cost. The extra cost should be deducted
// from the regular even the state reservoir is non-zero.
if used := st.gasUsed(); used < floorDataGas {
/*
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
}
*/
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(prev, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor)
prior, _ := st.gasRemaining.ChargeRegular(floorDataGas - used)
if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
}
}
if peakGasUsed < floorDataGas {
peakGasUsed = floorDataGas
}
peakGasUsed = max(peakGasUsed, floorDataGas)
peakRegular = max(peakRegular, floorDataGas)
}
returned := st.returnGas()
@ -766,19 +810,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// st.gasRemaining.UsedRegularGas / UsedStateGas already include both
// the intrinsic charge (from st.gasRemaining.Charge(cost) above) and
// the per-frame exec contributions absorbed from evm.Call / evm.Create.
// UsedStateGas may be negative when inline refunds (SSTORE 0→x→0,
// CREATE-failure refund, 7702 auth refund, same-tx-SD refund) exceed
// intrinsic + exec state charges. Clamp at 0.
//
// UsedStateGas should never become negative in the top-most frame, since
// state gas refunds only occur when state creation is reverted within the
// same transaction, while clearing pre-existing state is never refunded.
var txState uint64
if st.gasRemaining.UsedStateGas > 0 {
if st.gasRemaining.UsedStateGas >= 0 {
txState = uint64(st.gasRemaining.UsedStateGas)
} else {
log.Error("Negative top-most frame state gas usage", "amount", st.gasRemaining.UsedStateGas)
}
txRegular := max(st.gasRemaining.UsedRegularGas, floorDataGas)
if err := st.gp.ChargeGasAmsterdam(txRegular, txState, st.gasUsed()); err != nil {
if err := st.gp.ChargeGasAmsterdam(peakRegular, txState, st.gasUsed()); err != nil {
return nil, err
}
} else {
if err = st.gp.ReturnGas(returned, st.gasUsed()); err != nil {
if err = st.gp.ChargeGasLegacy(returned, st.gasUsed()); err != nil {
return nil, err
}
}
@ -909,18 +955,15 @@ func (st *stateTransition) calcRefund() uint64 {
return refund
}
// returnGas returns ETH for remaining gas,
// exchanged at the original rate.
// returnGas returns ETH for remaining gas, exchanged at the original rate.
func (st *stateTransition) returnGas() uint64 {
gas := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
remaining.Mul(remaining, st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
after := st.gasRemaining
after.RegularGas = 0
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
if !st.gasRemaining.IsZero() && st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), tracing.Gas{}, tracing.GasChangeTxLeftOverReturned)
}
return gas
}

View file

@ -472,6 +472,10 @@ const (
// transaction data. This change will always be a negative change.
GasChangeTxDataFloor GasChangeReason = 19
// GasChangeStateGasRefund represents the amount of pre-charged state gas
// refunded back to the state reservoir.
GasChangeStateGasRefund GasChangeReason = 20
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event.
GasChangeIgnored GasChangeReason = 0xFF

View file

@ -125,8 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
gasCostPerStateByte := core.CostPerStateByte(head, opts.Config)
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, gasCostPerStateByte)
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
if err != nil {
return err
}
@ -139,9 +138,18 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if err != nil {
return err
}
// Make sure the transaction has sufficient gas allowance to
// pay the floor cost.
if tx.Gas() < floorDataGas {
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
}
// In Amsterdam, the transaction gas limit is allowed to exceed
// params.MaxTxGas, but the calldata floor cost is capped by it.
if rules.IsAmsterdam {
if max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas {
return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas)
}
}
}
// Ensure the gasprice is high enough to cover the requirement of the calling pool
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {

View file

@ -152,9 +152,19 @@ func (c *Contract) chargeState(s uint64, logger *tracing.Hooks, reason tracing.G
return true
}
// RefundGas absorbs a sub-call's leftover GasBudget into this contract's gas
// refundState refunds the pre-charged state gas back to state reservoir.
func (c *Contract) refundState(s uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
prior := c.Gas
c.Gas.RefundState(s)
if s != 0 && logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
}
}
// refundGas absorbs a sub-call's leftover GasBudget into this contract's gas
// state. Thin wrapper around GasBudget.Absorb with tracer integration.
func (c *Contract) RefundGas(child GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) {
func (c *Contract) refundGas(child GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) {
prior := c.Gas
c.Gas.Absorb(child)
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {

View file

@ -264,7 +264,7 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - any error that occurred
func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks, rules params.Rules) (ret []byte, remaining GasBudget, err error) {
gasCost := p.RequiredGas(input)
prior, ok := gas.Charge(GasCosts{RegularGas: gasCost})
prior, ok := gas.ChargeRegular(gasCost)
if !ok {
return nil, gas, ErrOutOfGas
}

View file

@ -299,11 +299,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
}
if isPrecompile {
var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
} else {
// Initialise a new contract and set the code that is to be used by the EVM.
code := evm.resolveCode(addr)
@ -318,18 +314,20 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
gas = contract.Gas
}
}
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot. gasFromExec below handles the
// regular-gas burn on halt.
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err, initialStateGas)
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
// Drain the leftover regular gas if unexceptional halt occurs
if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
}
}
}
return ret, gas.ExitFromErr(err, initialStateGas), err
return ret, exitGas, err
}
// CallCode executes the contract associated with the addr with the given input
@ -361,11 +359,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
} 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.
@ -374,15 +368,20 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
ret, err = evm.Run(contract, input, false)
gas = contract.Gas
}
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err, initialStateGas)
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
// Drain the leftover regular gas if unexceptional halt occurs
if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
}
}
}
return ret, gas.ExitFromErr(err, initialStateGas), err
return ret, exitGas, err
}
// DelegateCall executes the contract associated with the addr with the given input
@ -409,26 +408,27 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
} else {
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
}
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err, initialStateGas)
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
// Drain the leftover regular gas if unexceptional halt occurs
if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
}
}
}
return ret, gas.ExitFromErr(err, initialStateGas), err
return ret, exitGas, err
}
// StaticCall executes the contract associated with the addr with the given input
@ -463,26 +463,25 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount)
if p, isPrecompile := evm.precompile(addr); isPrecompile {
var stateDB StateDB
if evm.chainRules.IsAmsterdam {
stateDB = evm.StateDB
}
ret, gas, err = RunPrecompiledContract(stateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
} else {
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.Run(contract, input, true)
gas = contract.Gas
}
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err, initialStateGas)
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
}
}
}
return ret, gas.ExitFromErr(err, initialStateGas), err
return ret, exitGas, err
}
// create creates a new contract using code as deployment code.
@ -593,12 +592,14 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
// state and gas is preserved (i.e., treated as success).
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot)
exit := contract.Gas.Exit(err, initialStateGas)
if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(contract.Gas.RegularGas, 0, tracing.GasChangeCallFailedExecution)
if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
}
}
return ret, address, contract.Gas.ExitFromErr(err, initialStateGas), err
return ret, address, exit, err
}
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
// Both packaged as a success-form GasBudget.

View file

@ -368,7 +368,7 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}
func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
expByteLen := uint64(((*stack.back(1)).BitLen() + 7) / 8)
expByteLen := uint64((stack.back(1).BitLen() + 7) / 8)
var (
gas = expByteLen * params.ExpByteFrontier // no overflow check required. Max is 256 * ExpByte gas
@ -381,7 +381,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
}
func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
expByteLen := uint64(((*stack.back(1)).BitLen() + 7) / 8)
expByteLen := uint64((stack.back(1).BitLen() + 7) / 8)
var (
gas = expByteLen * params.ExpByteEIP158 // no overflow check required. Max is 256 * ExpByte gas
@ -460,10 +460,10 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
return gas, nil
}
// gasCallIntrinsic8037 is the intrinsic gas calculator for CALL in Amsterdam.
// regularGasCall8037 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) {
func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
gas uint64
transfersValue = !stack.back(2).IsZero()
@ -571,7 +571,10 @@ func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
words := (size + 31) / 32
wordGas := params.InitCodeWordGas * words
stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte
return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil
return GasCosts{
RegularGas: gas + wordGas,
StateGas: stateGas,
}, nil
}
func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
@ -591,28 +594,35 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
}
// Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow
words := (size + 31) / 32
// CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas (for address hashing).
// CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas
// (for address hashing).
wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words
stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte
return GasCosts{RegularGas: gas + wordGas, StateGas: stateGas}, nil
return GasCosts{
RegularGas: gas + wordGas,
StateGas: stateGas,
}, nil
}
// gasCall8037 is the stateful gas calculator for CALL in Amsterdam (EIP-8037).
// stateGasCall8037 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) {
func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error) {
var (
gas GasCosts
gas uint64
transfersValue = !stack.back(2).IsZero()
address = common.Address(stack.back(1).Bytes20())
)
// TODO(rjl, marius), can EIP8037 implicitly means the EIP158 is also activated?
// It's technically possible to skip the EIP158 but very unlikely in practice.
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
} else if !evm.StateDB.Exist(address) {
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
return gas, nil
}
@ -671,14 +681,9 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
}
if original == current {
if original == (common.Hash{}) { // create slot (2.1.1)
// EIP-8037: Return both regular and state gas. System calls do not charge state gas.
var stateGas uint64
if !contract.IsSystemCall {
stateGas = params.StorageCreationSize * evm.Context.CostPerStateByte
}
return GasCosts{
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
StateGas: stateGas,
StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
}, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)

View file

@ -55,7 +55,7 @@ func (g GasCosts) String() string {
// in lockstep.
//
// - At frame exit: Preserved / ExitSuccess / ExitRevert / ExitHalt /
// ExitFromErr produce a new GasBudget in "leftover" form that packages
// Exit produce a new GasBudget in "leftover" form that packages
// the result for the caller.
//
// - At absorption: the caller's Absorb method merges the child's leftover
@ -263,7 +263,7 @@ func (g GasBudget) ExitHalt(initialStateGas uint64) GasBudget {
}
}
// ExitFromErr dispatches on err to the appropriate exit-form constructor
// Exit dispatches on err to the appropriate exit-form constructor
// for the post-evm.Run path:
//
// - err == nil → ExitSuccess
@ -272,7 +272,7 @@ func (g GasBudget) ExitHalt(initialStateGas uint64) GasBudget {
//
// Soft validation failures (occurring BEFORE evm.Run) should call Preserved
// directly instead of going through this dispatcher.
func (g GasBudget) ExitFromErr(err error, initialStateGas uint64) GasBudget {
func (g GasBudget) Exit(err error, initialStateGas uint64) GasBudget {
switch {
case err == nil:
return g.ExitSuccess()

View file

@ -675,7 +675,7 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte)
}
@ -710,11 +710,13 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte)
}
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// If the creation frame reverts or halts exceptionally, the charged state-gas
// is refilled back to the state reservoir in Amsterdam.
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeStateGasRefund)
}
if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer
return res, nil
@ -754,11 +756,24 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
temp.SetOne()
}
stack.push(&temp)
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// If the call frame reverts or halts exceptionally, the charged state-gas
// is refilled back to the state reservoir in Amsterdam.
//
// The state-gas should only be refunded if the state creation doesn't
// happens, such as ErrDepth, ErrInsufficientBalance.
//
// TODO(rjl) it's so ugly, please rework it.
if evm.chainRules.IsAmsterdam && err != nil {
if (err == ErrDepth || err == ErrInsufficientBalance) && !value.IsZero() && evm.StateDB.Empty(toAddr) {
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeStateGasRefund)
}
}
evm.returnData = ret
return ret, nil
@ -792,7 +807,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -821,7 +836,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil
@ -851,7 +866,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.RefundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret
return ret, nil

View file

@ -71,14 +71,6 @@ type StateDB interface {
// during the current transaction.
IsNewContract(addr common.Address) bool
// SameTxSelfDestructs returns addresses that were created and then
// self-destructed in the current transaction.
SameTxSelfDestructs() []common.Address
// NewStorageSlotCount returns the number of storage slots written to a
// non-zero value in the current transaction on the given address.
NewStorageSlotCount(addr common.Address) int
// Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool

View file

@ -28,6 +28,9 @@ type (
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)
regularGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error)
stateGasFunc func(*EVM, *Contract, *Stack) (uint64, error)
)
type operation struct {

View file

@ -267,7 +267,7 @@ var (
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(gasCallIntrinsic8037, gasCall8037)
innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(regularGasCall8037, stateGasCall8037)
)
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
@ -381,7 +381,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
// 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 {
func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
eip2929Cost uint64
@ -397,8 +397,8 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
}
}
// Compute intrinsic cost (memory + transfer, no new account creation).
intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
// Compute regular cost (memory + transfer, no new account creation).
regularCost, err := regularFunc(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
@ -406,7 +406,7 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
// 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.chargeRegular(intrinsicCost, evm.Config.Tracer, tracing.GasChangeCallOpCode) {
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallOpCode) {
return GasCosts{}, ErrOutOfGas
}
@ -424,13 +424,12 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
}
// Compute and charge state gas (new account creation) AFTER regular gas.
stateGas, err := stateGasFunc(evm, contract, stack, mem, memorySize)
stateGas, err := stateGasFunc(evm, contract, stack)
if err != nil {
return GasCosts{}, err
}
if stateGas.StateGas > 0 {
// Charge updates contract.Gas.UsedStateGas in lockstep.
if _, ok := contract.Gas.Charge(GasCosts{StateGas: stateGas.StateGas}); !ok {
if stateGas > 0 {
if _, ok := contract.Gas.ChargeState(stateGas); !ok {
return GasCosts{}, ErrOutOfGas
}
}
@ -443,8 +442,8 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
// Temporarily undo direct regular charges for tracer reporting.
// The interpreter will charge the returned totalCost.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost
contract.Gas.UsedRegularGas -= eip2929Cost + eip7702Cost + intrinsicCost
contract.Gas.RegularGas += eip2929Cost + eip7702Cost + regularCost
contract.Gas.UsedRegularGas -= eip2929Cost + eip7702Cost + regularCost
// Aggregate total cost.
var (
@ -454,7 +453,7 @@ func makeCallVariantGasCallEIP8037(intrinsicFunc intrinsicGasFunc, stateGasFunc
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
return GasCosts{}, ErrGasUintOverflow
}
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
if totalCost, overflow = math.SafeAdd(totalCost, regularCost); overflow {
return GasCosts{}, ErrGasUintOverflow
}
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {

View file

@ -199,10 +199,11 @@ const (
// don't consume block gas.
BALItemCost uint64 = 2000
AccountCreationSize = 112
StorageCreationSize = 32
AccountCreationSize = 120
StorageCreationSize = 64
AuthorizationCreationSize = 23
CostPerStateByte = 1174
CostPerStateByte = 1530
SystemMaxSStoresPerCall = 16
)
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation

View file

@ -81,7 +81,7 @@ func (tt *TransactionTest) Run() error {
return
}
// Intrinsic cost
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, 0)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
if err != nil {
return
}