diff --git a/core/vm/eips.go b/core/vm/eips.go index ceb74d51e6..18ce837792 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -1055,13 +1055,20 @@ func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] if interpreter.readOnly && !value.IsZero() { return nil, ErrWriteProtection } - if !value.IsZero() { - gas += params.CallStipend + + var ( + ret []byte + returnGas uint64 + err error + ) + if interpreter.evm.callGasTemp == 0 { + // zero temp call gas indicates a min retained gas error + ret, returnGas, err = nil, 0, ErrExecutionReverted + } else { + ret, returnGas, err = interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value) } - ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value) - - if err == ErrExecutionReverted { + if err == ErrExecutionReverted || err == ErrInsufficientBalance || err == ErrDepth { temp.SetOne() } else if err != nil { temp.SetUint64(2) @@ -1102,11 +1109,14 @@ func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCont err = ErrExecutionReverted ret = nil returnGas = gas + } else if interpreter.evm.callGasTemp == 0 { + // zero temp call gas indicates a min retained gas error + ret, returnGas, err = nil, 0, ErrExecutionReverted } else { ret, returnGas, err = interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas, true) } - if err == ErrExecutionReverted { + if err == ErrExecutionReverted || err == ErrDepth { temp.SetOne() } else if err != nil { temp.SetUint64(2) @@ -1136,8 +1146,19 @@ func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContex // Get arguments from the memory. args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) - ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) - if err == ErrExecutionReverted { + var ( + ret []byte + returnGas uint64 + err error + ) + if interpreter.evm.callGasTemp == 0 { + // zero temp call gas indicates a min retained gas error + ret, returnGas, err = nil, 0, ErrExecutionReverted + } else { + ret, returnGas, err = interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) + } + + if err == ErrExecutionReverted || err == ErrDepth { temp.SetOne() } else if err != nil { temp.SetUint64(2) diff --git a/core/vm/errors.go b/core/vm/errors.go index 95534ab96d..026d0b2533 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -42,7 +42,7 @@ var ( ErrInvalidEOFInitcode = errors.New("invalid eof initcode") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrInvalidNumberOfOutputs = errors.New("invalid number of outputs") - ErrInvalidNonReturningFlag = errors.New("Invalid non-returning flag, bad RETF") + ErrInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF") // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. diff --git a/core/vm/gas.go b/core/vm/gas.go index 5aaa7eb473..f77a271a43 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -17,6 +17,7 @@ package vm import ( + "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -53,3 +54,32 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u return callCost.Uint64(), nil } + +// extCallGas returns the actual gas cost for ext*call operations. +// +// EOF v1 includes EIP-150 rules (all but 1/64) with a floor of MIN_RETAINED_GAS (5000) +// and a minimum returned value of MIN_CALLE_GASS (2300). +// There is also no call gas, so all available gas is used. +// +// If the minimum retained gas constraint is violated, zero gas and no error is returned +func extCallGas(availableGas, base uint64) (uint64, error) { + if availableGas < base { + return 0, ErrOutOfGas + } + availableGas = availableGas - base + if availableGas < params.ExtCallMinRetainedGas { + return 0, nil + } + + retainedGas := availableGas / 64 + if retainedGas < params.ExtCallMinRetainedGas { + retainedGas = params.ExtCallMinRetainedGas + } + gas := availableGas - retainedGas + + if gas < params.ExtCallMinCalleeGas { + return 0, nil + } else { + return gas, nil + } +} diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index e79a56f053..73a2d63f22 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" - "github.com/holiman/uint256" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so @@ -484,37 +483,32 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo func gasExtCall(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()) + transfersValue = !stack.Back(3).IsZero() + address = common.Address(stack.Back(0).Bytes20()) + overflow bool ) - if evm.chainRules.IsEIP158 { - if transfersValue && evm.StateDB.Empty(address) { + if transfersValue { + if evm.StateDB.Empty(address) { gas += params.CallNewAccountGas } - } else if !evm.StateDB.Exist(address) { - gas += params.CallNewAccountGas - } - if transfersValue && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas + if evm.chainRules.IsEIP4762 { + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) + if overflow { + return 0, ErrGasUintOverflow + } + } else { + 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 } - if evm.chainRules.IsEIP4762 { - if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) - if overflow { - return 0, ErrGasUintOverflow - } - } - } - evm.callGasTemp, err = callGas(true, contract.Gas, gas, new(uint256.Int).SetUint64(contract.Gas)) + evm.callGasTemp, err = extCallGas(contract.Gas, gas) if err != nil { return 0, err } @@ -531,7 +525,7 @@ func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if err != nil { return 0, err } - evm.callGasTemp, err = callGas(true, contract.Gas, gas, new(uint256.Int).SetUint64(contract.Gas)) + evm.callGasTemp, err = extCallGas(contract.Gas, gas) if err != nil { return 0, err } @@ -547,7 +541,7 @@ func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m if err != nil { return 0, err } - evm.callGasTemp, err = callGas(true, contract.Gas, gas, new(uint256.Int).SetUint64(contract.Gas)) + evm.callGasTemp, err = extCallGas(contract.Gas, gas) if err != nil { return 0, err } diff --git a/params/protocol_params.go b/params/protocol_params.go index dcd0592938..3705f8ba0a 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -89,6 +89,8 @@ const ( CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. + ExtCallMinRetainedGas uint64 = 5000 // For EXT*CALL this is the minimum gas that the EIp158 1/64th rule must retain + ExtCallMinCalleeGas uint64 = 2300 // For EXT*CALL this is the minimum gas that must be passed to the callee, ignoring 63/64 TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)