mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-13 11:36:37 +00:00
core/vm: rework gas measurement for call variants (#33648)
EIP-7928 brings state reads into consensus by recording accounts and storage accessed during execution in the block access list. As part of the spec, we need to check that there is enough gas available to cover the cost component which doesn't depend on looking up state. If this component can't be covered by the available gas, we exit immediately. The portion of the call dynamic cost which doesn't depend on state look ups: - EIP2929 call costs - value transfer cost - memory expansion cost This PR: - breaks up the "inner" gas calculation for each call variant into a pair of stateless/stateful cost methods - modifies the gas calculation logic of calls to check stateless cost component first, and go out of gas immediately if it is not covered. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
parent
a3083ff5d0
commit
fd859638bd
3 changed files with 108 additions and 90 deletions
|
|
@ -49,6 +49,5 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u
|
||||||
if !callCost.IsUint64() {
|
if !callCost.IsUint64() {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
return callCost.Uint64(), nil
|
return callCost.Uint64(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,32 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
var (
|
||||||
|
gasCall = makeCallVariantGasCost(gasCallIntrinsic)
|
||||||
|
gasCallCode = makeCallVariantGasCost(gasCallCodeIntrinsic)
|
||||||
|
gasDelegateCall = makeCallVariantGasCost(gasDelegateCallIntrinsic)
|
||||||
|
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
||||||
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsic, stack.Back(0))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp)
|
||||||
|
if overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return gas, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
gas uint64
|
gas uint64
|
||||||
transfersValue = !stack.Back(2).IsZero()
|
transfersValue = !stack.Back(2).IsZero()
|
||||||
|
|
@ -382,38 +407,40 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||||
if evm.readOnly && transfersValue {
|
if evm.readOnly && transfersValue {
|
||||||
return 0, ErrWriteProtection
|
return 0, ErrWriteProtection
|
||||||
}
|
}
|
||||||
|
// Stateless check
|
||||||
if evm.chainRules.IsEIP158 {
|
|
||||||
if transfersValue && evm.StateDB.Empty(address) {
|
|
||||||
gas += params.CallNewAccountGas
|
|
||||||
}
|
|
||||||
} else if !evm.StateDB.Exist(address) {
|
|
||||||
gas += params.CallNewAccountGas
|
|
||||||
}
|
|
||||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
|
||||||
gas += params.CallValueTransferGas
|
|
||||||
}
|
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
var transferGas uint64
|
||||||
|
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||||
|
transferGas = params.CallValueTransferGas
|
||||||
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
// it can effectively prevent accessing the states in the following steps.
|
||||||
if err != nil {
|
if contract.Gas < gas {
|
||||||
return 0, err
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
// Stateful check
|
||||||
|
var stateGas uint64
|
||||||
|
if evm.chainRules.IsEIP158 {
|
||||||
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
|
stateGas += params.CallNewAccountGas
|
||||||
|
}
|
||||||
|
} else if !evm.StateDB.Exist(address) {
|
||||||
|
stateGas += params.CallNewAccountGas
|
||||||
|
}
|
||||||
|
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -428,46 +455,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
return memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var overflow bool
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
return memoryGasCost(mem, memorySize)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var overflow bool
|
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
|
||||||
return 0, ErrGasUintOverflow
|
|
||||||
}
|
|
||||||
return gas, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
|
|
||||||
|
|
@ -256,10 +256,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
|
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
|
||||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
|
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
|
||||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
|
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
|
||||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
|
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
|
||||||
)
|
)
|
||||||
|
|
||||||
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
|
|
@ -274,62 +274,85 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
||||||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
total uint64 // total dynamic gas used
|
eip2929Cost uint64
|
||||||
addr = common.Address(stack.Back(1).Bytes20())
|
eip7702Cost uint64
|
||||||
|
addr = common.Address(stack.Back(1).Bytes20())
|
||||||
)
|
)
|
||||||
|
// Perform EIP-2929 checks (stateless), checking address presence
|
||||||
// Check slot presence in the access list
|
// in the accessList and charge the cold access accordingly.
|
||||||
if !evm.StateDB.AddressInAccessList(addr) {
|
if !evm.StateDB.AddressInAccessList(addr) {
|
||||||
evm.StateDB.AddAddressToAccessList(addr)
|
evm.StateDB.AddAddressToAccessList(addr)
|
||||||
// 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
|
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form
|
||||||
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
// of a constant cost, so the cost to charge for cold access, if any,
|
||||||
// Charge the remaining difference here already, to correctly calculate available
|
// is Cold - Warm
|
||||||
// gas for call
|
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||||
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
|
||||||
|
// Charge the remaining difference here already, to correctly calculate
|
||||||
|
// available gas for call
|
||||||
|
if !contract.UseGas(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return 0, ErrOutOfGas
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
total += coldCost
|
}
|
||||||
|
|
||||||
|
// Perform the intrinsic cost calculation including:
|
||||||
|
//
|
||||||
|
// - transfer value
|
||||||
|
// - memory expansion
|
||||||
|
// - create new account
|
||||||
|
intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||||
|
// it can effectively prevent accessing the states in the following steps.
|
||||||
|
// It's an essential safeguard before any stateful check.
|
||||||
|
if contract.Gas < intrinsicCost {
|
||||||
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if code is a delegation and if so, charge for resolution.
|
// Check if code is a delegation and if so, charge for resolution.
|
||||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||||
var cost uint64
|
|
||||||
if evm.StateDB.AddressInAccessList(target) {
|
if evm.StateDB.AddressInAccessList(target) {
|
||||||
cost = params.WarmStorageReadCostEIP2929
|
eip7702Cost = params.WarmStorageReadCostEIP2929
|
||||||
} else {
|
} else {
|
||||||
evm.StateDB.AddAddressToAccessList(target)
|
evm.StateDB.AddAddressToAccessList(target)
|
||||||
cost = params.ColdAccountAccessCostEIP2929
|
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||||
}
|
}
|
||||||
if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
if !contract.UseGas(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||||
return 0, ErrOutOfGas
|
return 0, ErrOutOfGas
|
||||||
}
|
}
|
||||||
total += cost
|
|
||||||
}
|
}
|
||||||
|
// Calculate the gas budget for the nested call. The costs defined by
|
||||||
// Now call the old calculator, which takes into account
|
// EIP-2929 and EIP-7702 have already been applied.
|
||||||
// - create new account
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsicCost, stack.Back(0))
|
||||||
// - transfer value
|
|
||||||
// - memory expansion
|
|
||||||
// - 63/64ths rule
|
|
||||||
old, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return old, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporarily add the gas charge back to the contract and return value. By
|
// Temporarily add the gas charge back to the contract and return value. By
|
||||||
// adding it to the return, it will be charged outside of this function, as
|
// adding it to the return, it will be charged outside of this function, as
|
||||||
// part of the dynamic gas. This will ensure it is correctly reported to
|
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||||
// tracers.
|
// tracers.
|
||||||
contract.Gas += total
|
contract.Gas += eip2929Cost + eip7702Cost
|
||||||
|
|
||||||
var overflow bool
|
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
||||||
if total, overflow = math.SafeAdd(old, total); overflow {
|
// the CALL opcode itself, and the cost incurred by nested calls.
|
||||||
|
var (
|
||||||
|
overflow bool
|
||||||
|
totalCost uint64
|
||||||
|
)
|
||||||
|
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
|
||||||
return 0, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return total, nil
|
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
|
||||||
|
return 0, ErrGasUintOverflow
|
||||||
|
}
|
||||||
|
return totalCost, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue