core/vm: for selfdestruct/sstore whose gas funcs are dependent on reading state, move readOnly call context check into gas func to avoid unecessary state reads in the gas handler in case where these are called in a static context.

This commit is contained in:
Jared Wasinger 2025-11-25 12:13:58 -08:00
parent bd77b77ede
commit 115bc8ed33
3 changed files with 31 additions and 13 deletions

View file

@ -97,6 +97,9 @@ var (
)
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
var (
y, x = stack.Back(1), stack.Back(0)
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), x.Bytes32())
@ -181,6 +184,9 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
// (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
// (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
// If we fail the minimum gas availability invariant, fail (0)
if contract.Gas <= params.SstoreSentryGasEIP2200 {
return 0, errors.New("not enough gas for reentrancy sentry")
@ -374,6 +380,10 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
)
if evm.readOnly && transfersValue {
return 0, ErrWriteProtection
}
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
@ -462,6 +472,10 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
}
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
var gas uint64
// EIP150 homestead gas reprice fork:
if evm.chainRules.IsEIP150 {

View file

@ -518,9 +518,6 @@ func opSload(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opSstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly {
return nil, ErrWriteProtection
}
loc := scope.Stack.pop()
val := scope.Stack.pop()
evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32())
@ -743,9 +740,6 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get the arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
if evm.readOnly && !value.IsZero() {
return nil, ErrWriteProtection
}
if !value.IsZero() {
gas += params.CallStipend
}
@ -882,9 +876,6 @@ func opStop(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly {
return nil, ErrWriteProtection
}
beneficiary := scope.Stack.pop()
balance := evm.StateDB.GetBalance(scope.Contract.Address())
evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
@ -901,9 +892,6 @@ func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
}
func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly {
return nil, ErrWriteProtection
}
beneficiary := scope.Stack.pop()
balance := evm.StateDB.GetBalance(scope.Contract.Address())
evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)

View file

@ -28,6 +28,9 @@ import (
func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
if evm.readOnly {
return 0, ErrWriteProtection
}
// If we fail the minimum gas availability invariant, fail (0)
if contract.Gas <= params.SstoreSentryGasEIP2200 {
return 0, errors.New("not enough gas for reentrancy sentry")
@ -226,6 +229,11 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gas uint64
address = common.Address(stack.peek().Bytes20())
)
// TODO: EEST test that performs selfdestruct within a static context. no reads should be reported
// This is probably covered in the converted blockchain tests but need to double-check
if evm.readOnly {
return 0, ErrWriteProtection
}
if !evm.StateDB.AddressInAccessList(address) {
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address)
@ -244,12 +252,20 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
}
var (
gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
)
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
transfersValue := !stack.Back(2).IsZero()
if evm.readOnly && transfersValue {
return 0, ErrWriteProtection
}
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
}
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (