diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index c7c1274bf2..ccd50afbd6 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -368,12 +368,35 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor return gas, nil } -func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + ) + + if transfersValue && !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } + + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + + return gas, nil +} + +func gasCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 transfersValue = !stack.Back(2).IsZero() address = common.Address(stack.Back(1).Bytes20()) ) + if evm.chainRules.IsEIP158 { if transfersValue && evm.StateDB.Empty(address) { gas += params.CallNewAccountGas @@ -381,15 +404,22 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } else if !evm.StateDB.Exist(address) { gas += params.CallNewAccountGas } - if transfersValue && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas - } - memoryGas, err := memoryGasCost(mem, memorySize) + + return gas, nil +} +func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + stateless, err := gasCallStateless(evm, contract, stack, mem, memorySize) if err != nil { return 0, err } - var overflow bool - if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + + stateful, err := gasCallStateful(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + + gas, overflow := math.SafeAdd(stateless, stateful) + if overflow { return 0, ErrGasUintOverflow } @@ -404,7 +434,11 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return gas, nil } -func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallCodeStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + +func gasCallCodeStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err @@ -419,6 +453,16 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil +} + +func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var overflow bool + gas, err := gasCallCodeStateless(evm, contract, stack, 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 @@ -430,10 +474,16 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory } func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) + var ( + err error + gas uint64 + ) + + gas, err = gasDelegateCallStateless(evm, contract, stack, 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 @@ -445,11 +495,36 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me return gas, nil } -func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasDelegateCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + +func gasDelegateCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } + return gas, nil +} + +func gasStaticCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + return gas, nil +} + +func gasStaticCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + +func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasStaticCallStateless(evm, contract, stack, 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 diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index db53ede589..26d7570be0 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -155,7 +155,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } -func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc { +func makeCallVariantGasCallEIP2929(oldCalculatorStateful, oldCalculatorStateless gasFunc, addressPosition int) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.Back(addressPosition).Bytes20()) // Check slot presence in the access list @@ -163,6 +163,10 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // 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 coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + var ( + eip2929Cost uint64 + overflow bool + ) if !warmAccess { evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available @@ -170,35 +174,61 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } + eip2929Cost = coldCost } // Now call the old calculator, which takes into account // - create new account // - transfer value // - memory expansion // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - if warmAccess || err != nil { - return gas, err + + eip150BaseGas, err := oldCalculatorStateless(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err } + + // ensure the portion of the call cost which doesn't depend on state lookups + // is covered by the provided gas + if contract.Gas < eip150BaseGas { + return 0, ErrOutOfGas + } + + oldStateful, err := oldCalculatorStateful(evm, contract, stack, mem, memorySize) + if err != nil { + return oldStateful, err + } + + if eip150BaseGas, overflow = math.SafeAdd(eip150BaseGas, oldStateful); overflow { + return 0, ErrGasUintOverflow + } + + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, eip150BaseGas, stack.Back(0)) + if err != nil { + return 0, err + } + // In case of a cold access, we temporarily add the cold charge back, and also // add it to the returned gas. By adding it to the return, it will be charged // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers. - contract.Gas += coldCost - - var overflow bool - if gas, overflow = math.SafeAdd(gas, coldCost); overflow { + contract.Gas += eip2929Cost + var total uint64 + if total, overflow = math.SafeAdd(evm.callGasTemp, eip150BaseGas); overflow { return 0, ErrGasUintOverflow } - return gas, nil + if total, overflow = math.SafeAdd(total, eip2929Cost); overflow { + return 0, ErrGasUintOverflow + } + + return total, nil } } var ( - gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall, 1) - gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall, 1) - gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall, 1) - gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode, 1) + gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCallStateless, gasCallStateful, 1) + gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCallStateless, gasDelegateCallStateful, 1) + gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCallStateless, gasStaticCallStateful, 1) + gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCodeStateless, gasCallCodeStateful, 1) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) @@ -252,17 +282,21 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { } var ( - gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) - gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) - gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) - gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) + gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallStateful, gasCallStateless) + gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallStateful, gasDelegateCallStateless) + gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallStateful, gasStaticCallStateless) + gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeStateful, gasCallCodeStateless) ) -func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { +func makeCallVariantGasCallEIP7702(oldCalculatorStateful, oldCalculatorStateless gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( - total uint64 // total dynamic gas used - addr = common.Address(stack.Back(1).Bytes20()) + eip150BaseGas uint64 // gas used for memory expansion, transfer costs -> input to the 63/64 bounding + eip7702Gas uint64 + eip2929Gas uint64 + addr = common.Address(stack.Back(1).Bytes20()) + overflow bool + err error ) // Check slot presence in the access list @@ -276,44 +310,87 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } - total += coldCost + eip2929Gas = coldCost + } + eip150BaseGas, err = oldCalculatorStateless(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + + // ensure the portion of the call cost which doesn't depend on state lookups + // is covered by the provided gas + if contract.Gas < eip150BaseGas { + return 0, ErrOutOfGas + } + + // ^ TODO: I'm not totally sure this is compatible with the 63/64 gas reduction rule + + oldStateful, err := oldCalculatorStateful(evm, contract, stack, mem, memorySize) + if err != nil { + return oldStateful, err + } + + if eip150BaseGas, overflow = math.SafeAdd(eip150BaseGas, oldStateful); overflow { + return 0, ErrOutOfGas } // Check if code is a delegation and if so, charge for resolution. if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { - var cost uint64 if evm.StateDB.AddressInAccessList(target) { - cost = params.WarmStorageReadCostEIP2929 + eip7702Gas = params.WarmStorageReadCostEIP2929 } else { evm.StateDB.AddAddressToAccessList(target) - cost = params.ColdAccountAccessCostEIP2929 + eip7702Gas = params.ColdAccountAccessCostEIP2929 } - if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + if !contract.UseGas(eip7702Gas, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } - total += cost } - // Now call the old calculator, which takes into account - // - create new account - // - transfer value - // - memory expansion - // - 63/64ths rule - old, err := oldCalculator(evm, contract, stack, mem, memorySize) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, eip150BaseGas, stack.Back(0)) if err != nil { - return old, err + return 0, err } + // TODO: this check is probably not necessary (?) + /* + if contract.Gas < evm.callGasTemp { + return 0, ErrOutOfGas + } + */ + + // TODO: it's not clear what happens if there is enough gas to cover the stateless component + // but not enough to cover the whole call: do all the state reads happen in this case, and + // we fail at the very end? + // 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 // part of the dynamic gas. This will ensure it is correctly reported to // tracers. - contract.Gas += total - - var overflow bool - if total, overflow = math.SafeAdd(old, total); overflow { + contract.Gas, overflow = math.SafeAdd(contract.Gas, eip2929Gas) + if overflow { return 0, ErrGasUintOverflow } - return total, nil + contract.Gas, overflow = math.SafeAdd(contract.Gas, eip7702Gas) + if overflow { + return 0, ErrGasUintOverflow + } + + var totalCost uint64 + totalCost, overflow = math.SafeAdd(eip2929Gas, eip7702Gas) + if overflow { + return 0, ErrGasUintOverflow + } + totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp) + if overflow { + return 0, ErrGasUintOverflow + } + + totalCost, overflow = math.SafeAdd(totalCost, eip150BaseGas) + if overflow { + return 0, ErrGasUintOverflow + } + + return totalCost, nil } }