mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 02:40:45 +00:00
core/vm: align with current spec (claude)
All these should be removed once we updated the spec
This commit is contained in:
parent
ff799721ff
commit
ed7c83a28c
4 changed files with 100 additions and 22 deletions
|
|
@ -352,6 +352,7 @@ type stateTransition struct {
|
||||||
gp *GasPool
|
gp *GasPool
|
||||||
msg *Message
|
msg *Message
|
||||||
gasRemaining vm.GasBudget
|
gasRemaining vm.GasBudget
|
||||||
|
initReservoir uint64 // initial state-gas reservoir carved out of GasLimit (EIP-8037)
|
||||||
state vm.StateDB
|
state vm.StateDB
|
||||||
evm *vm.EVM
|
evm *vm.EVM
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +463,8 @@ func (st *stateTransition) buyGas() error {
|
||||||
if isAmsterdam {
|
if isAmsterdam {
|
||||||
limit = min(st.msg.GasLimit, params.MaxTxGas)
|
limit = min(st.msg.GasLimit, params.MaxTxGas)
|
||||||
}
|
}
|
||||||
st.gasRemaining = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
|
st.initReservoir = st.msg.GasLimit - limit
|
||||||
|
st.gasRemaining = vm.NewGasBudget(limit, st.initReservoir)
|
||||||
|
|
||||||
if st.evm.Config.Tracer.HasGasHook() {
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
|
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
|
||||||
|
|
@ -683,12 +685,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// Execute the transaction's creation.
|
// Execute the transaction's creation.
|
||||||
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||||
st.gasRemaining.Absorb(result)
|
st.gasRemaining.Absorb(result)
|
||||||
|
|
||||||
// If the contract creation failed, refund the account-creation state
|
|
||||||
// gas pre-charged in IntrinsicGas.
|
|
||||||
if rules.IsAmsterdam && vmerr != nil {
|
|
||||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Increment the nonce for the next transaction.
|
// Increment the nonce for the next transaction.
|
||||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||||
|
|
@ -709,6 +705,29 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
st.gasRemaining.Absorb(result)
|
st.gasRemaining.Absorb(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EIP-8037 (fork.py:1086): on any transaction error, the state gas
|
||||||
|
// consumed during *execution* is discarded — those state changes are
|
||||||
|
// reverted, so the charge is restored to the reservoir and not counted
|
||||||
|
// toward block_state_gas_used. The intrinsic state gas (CREATE new-account
|
||||||
|
// and EIP-7702 authorization charges) is tracked separately by the spec and
|
||||||
|
// is NOT discarded here; the CREATE new-account portion is refunded above
|
||||||
|
// via its dedicated RefundState. The frame-level Exit forms already refund
|
||||||
|
// state gas on a reverting/halting sub-call, but a top-level frame that
|
||||||
|
// ends in a code-deposit halt (or any other tx-level vmerr) can leave
|
||||||
|
// accumulated execution UsedStateGas that must be discarded here.
|
||||||
|
if rules.IsAmsterdam && vmerr != nil {
|
||||||
|
executionStateGas := st.gasRemaining.UsedStateGas - int64(cost.StateGas)
|
||||||
|
if executionStateGas > 0 {
|
||||||
|
st.gasRemaining.RefundState(uint64(executionStateGas))
|
||||||
|
}
|
||||||
|
// Additionally, a failed CREATE transaction refunds the intrinsic
|
||||||
|
// account-creation state gas pre-charged in IntrinsicGas (fork.py:1093:
|
||||||
|
// when tx.to is Bytes0 the NEW_ACCOUNT charge is added to state_refund).
|
||||||
|
if contractCreation {
|
||||||
|
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Settle down the gas usage and refund the ETH back if any remaining
|
// Settle down the gas usage and refund the ETH back if any remaining
|
||||||
gasUsed, peakUsed, err := st.settleGas(rules, floorDataGas)
|
gasUsed, peakUsed, err := st.settleGas(rules, floorDataGas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -781,6 +800,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
||||||
if st.gasRemaining.UsedStateGas < 0 {
|
if st.gasRemaining.UsedStateGas < 0 {
|
||||||
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
txStateGas := uint64(st.gasRemaining.UsedStateGas)
|
txStateGas := uint64(st.gasRemaining.UsedStateGas)
|
||||||
|
|
||||||
// EIP-8037:
|
// EIP-8037:
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,14 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
||||||
}
|
}
|
||||||
evm.StateDB.CreateAccount(addr)
|
evm.StateDB.CreateAccount(addr)
|
||||||
}
|
}
|
||||||
if evm.chainRules.IsAmsterdam && !value.IsZero() && evm.StateDB.Empty(addr) {
|
// EIP-8037: a value-bearing CALL to an empty account pays NEW_ACCOUNT state
|
||||||
|
// gas. For nested calls this is charged on the caller frame by the dynamic
|
||||||
|
// gas table (gasCallIntrinsic), matching the spec's inline charge_state_gas
|
||||||
|
// in system.call. Only the top-most call (depth 0) — which is dispatched
|
||||||
|
// straight to evm.Call without passing through that gas table — needs the
|
||||||
|
// charge applied here, against the forwarded budget. Charging in both places
|
||||||
|
// would double-count the new account.
|
||||||
|
if evm.depth == 0 && evm.chainRules.IsAmsterdam && !value.IsZero() && evm.StateDB.Empty(addr) {
|
||||||
prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte)
|
prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte)
|
||||||
if !ok {
|
if !ok {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
|
@ -597,14 +604,23 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
contract.SetCallCode(common.Hash{}, code)
|
contract.SetCallCode(common.Hash{}, code)
|
||||||
contract.IsDeployment = true
|
contract.IsDeployment = true
|
||||||
|
|
||||||
ret, err = evm.initNewContract(contract, address)
|
var depositHalt bool
|
||||||
|
ret, depositHalt, err = evm.initNewContract(contract, address)
|
||||||
|
|
||||||
// Special case: ErrCodeStoreOutOfGas pre-Homestead does NOT roll back
|
// Special case: ErrCodeStoreOutOfGas pre-Homestead does NOT roll back
|
||||||
// state and gas is preserved (i.e., treated as success).
|
// state and gas is preserved (i.e., treated as success).
|
||||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
exit := contract.Gas.Exit(err)
|
// EIP-8037: a code-deposit halt (initcode body succeeded, deposit step
|
||||||
|
// failed) keeps the state gas the body consumed for discard at the tx
|
||||||
|
// level, rather than refunding the reservoir like a mid-execution halt.
|
||||||
|
var exit GasBudget
|
||||||
|
if depositHalt && evm.chainRules.IsAmsterdam {
|
||||||
|
exit = contract.Gas.ExitCodeDepositHalt()
|
||||||
|
} else {
|
||||||
|
exit = contract.Gas.Exit(err)
|
||||||
|
}
|
||||||
if err != ErrExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
|
|
@ -619,54 +635,60 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
|
|
||||||
// initNewContract runs a new contract's creation code, performs checks on the
|
// initNewContract runs a new contract's creation code, performs checks on the
|
||||||
// resulting code that is to be deployed, and consumes necessary gas.
|
// resulting code that is to be deployed, and consumes necessary gas.
|
||||||
func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]byte, error) {
|
//
|
||||||
ret, err := evm.Run(contract, nil, false)
|
// The returned depositHalt flag is true when the initcode body itself ran to
|
||||||
|
// completion successfully but a subsequent code-deposit check failed (oversized
|
||||||
|
// code, 0xEF prefix, or insufficient gas for the hash/deposit charge). Under
|
||||||
|
// EIP-8037 this halt is metered differently from a mid-execution halt: the
|
||||||
|
// state gas consumed by the (successful) body is kept rather than refunded.
|
||||||
|
func (evm *EVM) initNewContract(contract *Contract, address common.Address) (ret []byte, depositHalt bool, err error) {
|
||||||
|
ret, err = evm.Run(contract, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ret, err
|
return ret, false, err
|
||||||
}
|
}
|
||||||
// Check prefix before gas calculation.
|
// Check prefix before gas calculation.
|
||||||
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
||||||
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
|
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
|
||||||
return ret, ErrInvalidCode
|
return ret, true, ErrInvalidCode
|
||||||
}
|
}
|
||||||
if evm.chainRules.IsEIP4762 {
|
if evm.chainRules.IsEIP4762 {
|
||||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
|
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
|
||||||
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||||
if len(ret) > 0 && (consumed < wanted) {
|
if len(ret) > 0 && (consumed < wanted) {
|
||||||
return ret, ErrCodeStoreOutOfGas
|
return ret, true, ErrCodeStoreOutOfGas
|
||||||
}
|
}
|
||||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||||
return ret, err
|
return ret, true, err
|
||||||
}
|
}
|
||||||
} else if evm.chainRules.IsAmsterdam {
|
} else if evm.chainRules.IsAmsterdam {
|
||||||
// Check max code size BEFORE charging gas so over-max code
|
// Check max code size BEFORE charging gas so over-max code
|
||||||
// does not consume state gas (which would inflate tx_state).
|
// does not consume state gas (which would inflate tx_state).
|
||||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||||
return ret, err
|
return ret, true, err
|
||||||
}
|
}
|
||||||
// Charge regular gas (hash cost) before state gas.
|
// Charge regular gas (hash cost) before state gas.
|
||||||
regularCost := toWordSize(uint64(len(ret))) * params.Keccak256WordGas
|
regularCost := toWordSize(uint64(len(ret))) * params.Keccak256WordGas
|
||||||
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
return ret, ErrCodeStoreOutOfGas
|
return ret, true, ErrCodeStoreOutOfGas
|
||||||
}
|
}
|
||||||
// Charge state gas (code-deposit) afterwards.
|
// Charge state gas (code-deposit) afterwards.
|
||||||
stateCost := uint64(len(ret)) * evm.Context.CostPerStateByte
|
stateCost := uint64(len(ret)) * evm.Context.CostPerStateByte
|
||||||
if !contract.chargeState(stateCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
if !contract.chargeState(stateCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
return ret, ErrCodeStoreOutOfGas
|
return ret, true, ErrCodeStoreOutOfGas
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
createDataCost := uint64(len(ret)) * params.CreateDataGas
|
createDataCost := uint64(len(ret)) * params.CreateDataGas
|
||||||
if !contract.chargeRegular(createDataCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
if !contract.chargeRegular(createDataCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||||
return ret, ErrCodeStoreOutOfGas
|
return ret, true, ErrCodeStoreOutOfGas
|
||||||
}
|
}
|
||||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||||
return ret, err
|
return ret, true, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(ret) > 0 {
|
if len(ret) > 0 {
|
||||||
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a new contract using code as deployment code.
|
// Create creates a new contract using code as deployment code.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -502,6 +503,21 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
||||||
return 0, ErrOutOfGas
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
// Stateful check
|
// Stateful check
|
||||||
|
if evm.chainRules.IsAmsterdam {
|
||||||
|
// EIP-8037: the cost of creating a new account via a value-bearing
|
||||||
|
// CALL is metered as state gas (NEW_ACCOUNT * CostPerStateByte),
|
||||||
|
// not the legacy regular CallNewAccountGas. Charge it directly here
|
||||||
|
// so it drains the state reservoir (spilling into regular gas only
|
||||||
|
// when the reservoir is exhausted), mirroring the spec's inline
|
||||||
|
// charge_state_gas call in system.call.
|
||||||
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
|
stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||||
|
if !contract.chargeState(stateGas, evm.Config.Tracer, tracing.GasChangeAccountCreation) {
|
||||||
|
return 0, ErrOutOfGas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
var stateGas uint64
|
var stateGas uint64
|
||||||
if evm.chainRules.IsEIP158 {
|
if evm.chainRules.IsEIP158 {
|
||||||
if transfersValue && evm.StateDB.Empty(address) {
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,26 @@ func (g GasBudget) ExitHalt() GasBudget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExitCodeDepositHalt produces the leftover for a CREATE/CREATE2 frame whose
|
||||||
|
// initcode body ran to completion but then failed during the code-deposit step
|
||||||
|
// (oversized code, 0xEF prefix, or insufficient gas for the hash/deposit
|
||||||
|
// charge). Per the spec's process_create_message exception handler, this path
|
||||||
|
// differs from a mid-execution ExceptionalHalt: only the remaining regular gas
|
||||||
|
// is burned, while the state gas the (successful) body consumed is KEPT in
|
||||||
|
// UsedStateGas rather than refunded to the reservoir. The state changes are
|
||||||
|
// reverted by the caller, but the state-gas accounting is propagated upward so
|
||||||
|
// the top-level tx settlement can discard it as the state dimension (rather
|
||||||
|
// than folding it back into the combined balance, which would wrongly deflate
|
||||||
|
// the regular dimension).
|
||||||
|
func (g GasBudget) ExitCodeDepositHalt() GasBudget {
|
||||||
|
return GasBudget{
|
||||||
|
RegularGas: 0,
|
||||||
|
StateGas: g.StateGas,
|
||||||
|
UsedRegularGas: g.UsedRegularGas + g.RegularGas,
|
||||||
|
UsedStateGas: g.UsedStateGas,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Exit 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:
|
// for the post-evm.Run path:
|
||||||
//
|
//
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue