nimbus-eth1/nimbus/evm/interpreter/op_handlers/oph_call.nim

549 lines
19 KiB
Nim

# Nimbus
# Copyright (c) 2021-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
## EVM Opcode Handlers: Call Operations
## ====================================
##
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
import
../../../constants,
../../../errors,
../../../common/evmforks,
../../computation,
../../memory,
../../stack,
../../types,
../../async/operations,
../gas_costs,
../gas_meter,
../op_codes,
../utils/utils_numeric,
./oph_defs,
chronicles,
eth/common,
eth/common/eth_types,
stint
when not defined(evmc_enabled):
import
../../state,
../../../db/ledger
# ------------------------------------------------------------------------------
# Private
# ------------------------------------------------------------------------------
type
LocalParams = tuple
gas: UInt256
value: UInt256
codeAddress: EthAddress
sender: EthAddress
memInPos: int
memInLen: int
memOutPos: int
memOutLen: int
flags: MsgFlags
memOffset: int
memLength: int
contractAddress: EthAddress
gasCallEIP2929: GasInt
proc updateStackAndParams(q: var LocalParams; c: Computation) =
c.stack.push(0)
let
outLen = calcMemSize(q.memOutPos, q.memOutLen)
inLen = calcMemSize(q.memInPos, q.memInLen)
# get the bigger one
if outLen < inLen:
q.memOffset = q.memInPos
q.memLength = q.memInLen
else:
q.memOffset = q.memOutPos
q.memLength = q.memOutLen
# EIP2929: This came before old gas calculator
# because it will affect `c.gasMeter.gasRemaining`
# and further `childGasLimit`
if FkBerlin <= c.fork:
when evmc_enabled:
if c.host.accessAccount(q.codeAddress) == EVMC_ACCESS_COLD:
q.gasCallEIP2929 = ColdAccountAccessCost - WarmStorageReadCost
else:
c.vmState.mutateStateDB:
if not db.inAccessList(q.codeAddress):
db.accessList(q.codeAddress)
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
q.gasCallEIP2929 = ColdAccountAccessCost - WarmStorageReadCost
proc callParams(c: Computation): LocalParams =
## Helper for callOp()
result.gas = c.stack.popInt()
result.codeAddress = c.stack.popAddress()
result.value = c.stack.popInt()
result.memInPos = c.stack.popInt().cleanMemRef
result.memInLen = c.stack.popInt().cleanMemRef
result.memOutPos = c.stack.popInt().cleanMemRef
result.memOutLen = c.stack.popInt().cleanMemRef
result.sender = c.msg.contractAddress
result.flags = c.msg.flags
result.contractAddress = result.codeAddress
result.updateStackAndParams(c)
proc callCodeParams(c: Computation): LocalParams =
## Helper for callCodeOp()
result = c.callParams
result.contractAddress = c.msg.contractAddress
proc delegateCallParams(c: Computation): LocalParams =
## Helper for delegateCall()
result.gas = c.stack.popInt()
result.codeAddress = c.stack.popAddress()
result.memInPos = c.stack.popInt().cleanMemRef
result.memInLen = c.stack.popInt().cleanMemRef
result.memOutPos = c.stack.popInt().cleanMemRef
result.memOutLen = c.stack.popInt().cleanMemRef
result.value = c.msg.value
result.sender = c.msg.sender
result.flags = c.msg.flags
result.contractAddress = c.msg.contractAddress
result.updateStackAndParams(c)
proc staticCallParams(c: Computation): LocalParams =
## Helper for staticCall()
result.gas = c.stack.popInt()
result.codeAddress = c.stack.popAddress()
result.memInPos = c.stack.popInt().cleanMemRef
result.memInLen = c.stack.popInt().cleanMemRef
result.memOutPos = c.stack.popInt().cleanMemRef
result.memOutLen = c.stack.popInt().cleanMemRef
result.value = 0.u256
result.sender = c.msg.contractAddress
result.flags.incl EVMC_STATIC
result.contractAddress = result.codeAddress
result.updateStackAndParams(c)
when evmc_enabled:
template execSubCall(c: Computation; msg: ref nimbus_message; p: LocalParams) =
c.chainTo(msg, shouldRaise = true):
c.returnData = @(makeOpenArray(c.res.output_data, c.res.output_size.int))
let actualOutputSize = min(p.memOutLen, c.returnData.len)
if actualOutputSize > 0:
c.memory.write(p.memOutPos,
c.returnData.toOpenArray(0, actualOutputSize - 1))
c.gasMeter.returnGas(c.res.gas_left)
if c.res.status_code == EVMC_SUCCESS:
c.stack.top(1)
if not c.res.release.isNil:
c.res.release(c.res)
else:
proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) {.raises: [].} =
## Call new VM -- helper for `Call`-like operations
# need to provide explicit <c> and <child> for capturing in chainTo proc()
# <memPos> and <memLen> are provided by value and need not be captured
var
child = newComputation(c.vmState, false, childMsg)
c.chainTo(child, shouldRaise = true):
if not child.shouldBurnGas:
c.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
c.merge(child)
c.stack.top(1)
c.returnData = child.output
let actualOutputSize = min(memLen, child.output.len)
if actualOutputSize > 0:
c.memory.write(memPos, child.output.toOpenArray(0, actualOutputSize - 1))
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
callOp: Vm2OpFn = proc(k: var Vm2Ctx) =
## 0xf1, Message-Call into an account
let cpt = k.cpt
if EVMC_STATIC in cpt.msg.flags and cpt.stack[^3, UInt256] > 0.u256:
raise newException(
StaticContextError,
"Cannot modify state while inside of a STATICCALL context")
let
p = cpt.callParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
cpt.asyncChainToRaise(ifNecessaryGetCodeForAccounts(cpt.vmState, @[p.contractAddress, p.codeAddress]), [CatchableError]):
var (gasCost, childGasLimit) = cpt.gasCosts[Call].c_handler(
p.value,
GasParams(
kind: Call,
c_isNewAccount: not cpt.accountExists(p.contractAddress),
c_gasBalance: cpt.gasMeter.gasRemaining - p.gasCallEIP2929,
c_contractGas: p.gas,
c_currentMemSize: cpt.memory.len,
c_memOffset: p.memOffset,
c_memLength: p.memLength))
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(Call, gasCost, reason = $Call)
cpt.returnData.setLen(0)
if cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = cpt.msg.depth
cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (call)")
cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen)
let senderBalance = cpt.getBalance(p.sender)
if senderBalance < p.value:
cpt.gasMeter.returnGas(childGasLimit)
return
when evmc_enabled:
let
msg = new(nimbus_message)
c = cpt
msg[] = nimbus_message(
kind : EVMC_CALL,
depth : (cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags
)
c.execSubCall(msg, p)
else:
cpt.execSubCall(
memPos = p.memOutPos,
memLen = p.memOutLen,
childMsg = Message(
kind: EVMC_CALL,
depth: cpt.msg.depth + 1,
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.codeAddress,
value: p.value,
data: cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
# ---------------------
callCodeOp: Vm2OpFn = proc(k: var Vm2Ctx) =
## 0xf2, Message-call into this account with an alternative account's code.
let
cpt = k.cpt
p = cpt.callCodeParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
cpt.asyncChainToRaise(ifNecessaryGetCodeForAccounts(cpt.vmState, @[p.contractAddress, p.codeAddress]), [CatchableError]):
var (gasCost, childGasLimit) = cpt.gasCosts[CallCode].c_handler(
p.value,
GasParams(
kind: CallCode,
c_isNewAccount: not cpt.accountExists(p.contractAddress),
c_gasBalance: cpt.gasMeter.gasRemaining - p.gasCallEIP2929,
c_contractGas: p.gas,
c_currentMemSize: cpt.memory.len,
c_memOffset: p.memOffset,
c_memLength: p.memLength))
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(CallCode, gasCost, reason = $CallCode)
cpt.returnData.setLen(0)
if cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = cpt.msg.depth
cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (callCode)")
cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen)
let senderBalance = cpt.getBalance(p.sender)
if senderBalance < p.value:
cpt.gasMeter.returnGas(childGasLimit)
return
when evmc_enabled:
let
msg = new(nimbus_message)
c = cpt
msg[] = nimbus_message(
kind : EVMC_CALLCODE,
depth : (cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags
)
c.execSubCall(msg, p)
else:
cpt.execSubCall(
memPos = p.memOutPos,
memLen = p.memOutLen,
childMsg = Message(
kind: EVMC_CALLCODE,
depth: cpt.msg.depth + 1,
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.codeAddress,
value: p.value,
data: cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
# ---------------------
delegateCallOp: Vm2OpFn = proc(k: var Vm2Ctx) =
## 0xf4, Message-call into this account with an alternative account's
## code, but persisting the current values for sender and value.
let
cpt = k.cpt
p = cpt.delegateCallParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
cpt.asyncChainToRaise(ifNecessaryGetCodeForAccounts(cpt.vmState, @[p.contractAddress, p.codeAddress]), [CatchableError]):
var (gasCost, childGasLimit) = cpt.gasCosts[DelegateCall].c_handler(
p.value,
GasParams(
kind: DelegateCall,
c_isNewAccount: not cpt.accountExists(p.contractAddress),
c_gasBalance: cpt.gasMeter.gasRemaining - p.gasCallEIP2929,
c_contractGas: p.gas,
c_currentMemSize: cpt.memory.len,
c_memOffset: p.memOffset,
c_memLength: p.memLength))
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(DelegateCall, gasCost, reason = $DelegateCall)
cpt.returnData.setLen(0)
if cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = cpt.msg.depth
cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (delegateCall)")
cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen)
when evmc_enabled:
let
msg = new(nimbus_message)
c = cpt
msg[] = nimbus_message(
kind : EVMC_DELEGATECALL,
depth : (cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags
)
c.execSubCall(msg, p)
else:
cpt.execSubCall(
memPos = p.memOutPos,
memLen = p.memOutLen,
childMsg = Message(
kind: EVMC_DELEGATECALL,
depth: cpt.msg.depth + 1,
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.codeAddress,
value: p.value,
data: cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
# ---------------------
staticCallOp: Vm2OpFn = proc(k: var Vm2Ctx) =
## 0xfa, Static message-call into an account.
let
cpt = k.cpt
p = cpt.staticCallParams
cpt.asyncChainTo(ifNecessaryGetAccounts(cpt.vmState, @[p.sender])):
cpt.asyncChainToRaise(ifNecessaryGetCodeForAccounts(cpt.vmState, @[p.contractAddress, p.codeAddress]), [CatchableError]):
var (gasCost, childGasLimit) = cpt.gasCosts[StaticCall].c_handler(
p.value,
GasParams(
kind: StaticCall,
c_isNewAccount: not cpt.accountExists(p.contractAddress),
c_gasBalance: cpt.gasMeter.gasRemaining - p.gasCallEIP2929,
c_contractGas: p.gas,
c_currentMemSize: cpt.memory.len,
c_memOffset: p.memOffset,
c_memLength: p.memLength))
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(StaticCall, gasCost, reason = $StaticCall)
cpt.returnData.setLen(0)
if cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = cpt.msg.depth
cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (staticCall)")
cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen)
when evmc_enabled:
let
msg = new(nimbus_message)
c = cpt
msg[] = nimbus_message(
kind : EVMC_CALL,
depth : (cpt.msg.depth + 1).int32,
gas : childGasLimit,
sender : p.sender,
recipient : p.contractAddress,
code_address: p.codeAddress,
input_data : cpt.memory.readPtr(p.memInPos),
input_size : p.memInLen.uint,
value : toEvmc(p.value),
flags : p.flags
)
c.execSubCall(msg, p)
else:
cpt.execSubCall(
memPos = p.memOutPos,
memLen = p.memOutLen,
childMsg = Message(
kind: EVMC_CALL,
depth: cpt.msg.depth + 1,
gas: childGasLimit,
sender: p.sender,
contractAddress: p.contractAddress,
codeAddress: p.codeAddress,
value: p.value,
data: cpt.memory.read(p.memInPos, p.memInLen),
flags: p.flags))
# ------------------------------------------------------------------------------
# Public, op exec table entries
# ------------------------------------------------------------------------------
const
vm2OpExecCall*: seq[Vm2OpExec] = @[
(opCode: Call, ## 0xf1, Message-Call into an account
forks: Vm2OpAllForks,
name: "call",
info: "Message-Call into an account",
exec: (prep: vm2OpIgnore,
run: callOp,
post: vm2OpIgnore)),
(opCode: CallCode, ## 0xf2, Message-Call with alternative code
forks: Vm2OpAllForks,
name: "callCode",
info: "Message-call into this account with alternative account's code",
exec: (prep: vm2OpIgnore,
run: callCodeOp,
post: vm2OpIgnore)),
(opCode: DelegateCall, ## 0xf4, CallCode with persisting sender and value
forks: Vm2OpHomesteadAndLater,
name: "delegateCall",
info: "Message-call into this account with an alternative account's " &
"code but persisting the current values for sender and value.",
exec: (prep: vm2OpIgnore,
run: delegateCallOp,
post: vm2OpIgnore)),
(opCode: StaticCall, ## 0xfa, Static message-call into an account
forks: Vm2OpByzantiumAndLater,
name: "staticCall",
info: "Static message-call into an account",
exec: (prep: vm2OpIgnore,
run: staticCallOp,
post: vm2OpIgnore))]
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------