re-integrated call op handlers

This commit is contained in:
Jordan Hrycaj 2021-04-16 16:06:02 +01:00 committed by zah
parent 1bdbfda37f
commit 4ac32d360b
5 changed files with 634 additions and 345 deletions

View File

@ -22,7 +22,7 @@ import
./op_handlers/[oph_defs,
oph_arithmetic, oph_hash, oph_envinfo, oph_blockdata,
oph_memory, oph_push, oph_dup, oph_swap, oph_log,
oph_create, oph_sysops]
oph_create, oph_call, oph_sysops]
# ------------------------------------------------------------------------------
# Helper
@ -52,7 +52,7 @@ proc mkOpTable(select: Fork): array[Op,Vm2OpExec] {.compileTime.} =
result.importList(select, vm2OpExecSwap, "Swap")
result.importList(select, vm2OpExecLog, "Log")
result.importList(select, vm2OpExecCreate, "Create")
#result.importList(select, vm2OpExecCall, "Call")
result.importList(select, vm2OpExecCall, "Call")
result.importList(select, vm2OpExecSysOp, "SysOp")
for op in Op:

View File

@ -0,0 +1,625 @@
# Nimbus
# Copyright (c) 2018 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
## ====================================
##
const
kludge {.intdefine.}: int = 0
breakCircularDependency {.used.} = kludge > 0
import
../../../errors,
./oph_defs,
chronicles,
stint
# ------------------------------------------------------------------------------
# Kludge BEGIN
# ------------------------------------------------------------------------------
when not breakCircularDependency:
import
../../../db/accounts_cache,
../../../constants,
../../stack,
../../v2computation,
../../v2memory,
../../v2state,
../../v2types,
../gas_meter,
../utils/v2utils_numeric,
../v2gas_costs,
eth/common
else:
import macros
type
MsgFlags = int
GasResult = tuple[gasCost, gasRefund: GasInt]
const
evmcCall = 42
evmcDelegateCall = 43
evmcCallCode = 44
emvcStatic = 45
MaxCallDepth = 46
ColdAccountAccessCost = 47
WarmStorageReadCost = 48
# function stubs from stack.nim (to satisfy compiler logic)
proc `[]`(x: Stack, i: BackwardsIndex; T: typedesc): T = result
proc top[T](x: Stack, value: T) = discard
proc push[T](x: Stack; n: T) = discard
proc popAddress(x: var Stack): EthAddress = result
proc popInt(x: var Stack): UInt256 = result
# function stubs from v2computation.nim (to satisfy compiler logic)
proc gasCosts(c: Computation): array[Op,int] = result
proc getBalance[T](c: Computation, address: T): Uint256 = result
proc newComputation[A,B](v:A, m:B, salt = 0.u256): Computation = new result
func shouldBurnGas(c: Computation): bool = result
proc accountExists(c: Computation, address: EthAddress): bool = result
proc isSuccess(c: Computation): bool = result
proc merge(c, child: Computation) = discard
template chainTo(c, d: Computation, e: untyped) =
c.child = d; c.continuation = proc() = e
# function stubs from v2utils_numeric.nim
func calcMemSize*(offset, length: int): int = result
# function stubs from v2memory.nim
proc len(mem: Memory): int = result
proc extend(mem: var Memory; startPos: Natural; size: Natural) = discard
proc read(mem: var Memory, startPos: Natural, size: Natural): seq[byte] = @[]
proc write(mem: var Memory, startPos: Natural, val: openarray[byte]) = discard
# function stubs from v2state.nim
template mutateStateDB(vmState: BaseVMState, body: untyped) =
block:
var db {.inject.} = vmState.accountDb
body
# function stubs from gas_meter.nim
proc consumeGas(gasMeter: var GasMeter; amount: int; reason: string) = discard
proc returnGas(gasMeter: var GasMeter; amount: GasInt) = discard
# function stubs from v2utils_numeric.nim
func cleanMemRef(x: UInt256): int = result
# stubs from v2gas_costs.nim
type GasParams = object
case kind*: Op
of Call, CallCode, DelegateCall, StaticCall:
c_isNewAccount: bool
c_contractGas: Uint256
c_gasBalance, c_currentMemSize, c_memOffset, c_memLength: int64
else:
discard
proc c_handler(x: int; y: Uint256, z: GasParams): GasResult = result
# function stubs from accounts_cache.nim:
func inAccessList[A,B](ac: A; address: B): bool = result
proc accessList[A,B](ac: var A; address: B) = discard
# ------------------------------------------------------------------------------
# Kludge END
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Private
# ------------------------------------------------------------------------------
proc callParams(c: Computation): (UInt256, UInt256, EthAddress,
EthAddress, int, int, int,
int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
let value = c.stack.popInt()
result = (gas,
value,
destination,
c.msg.contractAddress, # sender
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.msg.flags)
c.stack.push(0)
proc callCodeParams(c: Computation): (UInt256, UInt256, EthAddress,
EthAddress, int, int, int,
int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
let value = c.stack.popInt()
result = (gas,
value,
destination,
c.msg.contractAddress, # sender
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.msg.flags)
c.stack.push(0)
proc delegateCallParams(c: Computation): (UInt256, UInt256, EthAddress,
EthAddress, int, int, int,
int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
result = (gas,
c.msg.value, # value
destination,
c.msg.sender, # sender
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.msg.flags)
c.stack.push(0)
proc staticCallParams(c: Computation): (UInt256, UInt256, EthAddress,
EthAddress, int, int, int,
int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
result = (gas,
0.u256, # value
destination,
c.msg.contractAddress, # sender
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
emvcStatic) # is_static
c.stack.push(0)
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
callOp: Vm2OpFn = proc(k: Vm2Ctx) =
## 0xf1, Message-Call into an account
if emvcStatic == k.cpt.msg.flags and k.cpt.stack[^3, UInt256] > 0.u256:
raise newException(
StaticContextError,
"Cannot modify state while inside of a STATICCALL context")
let (gas, value, destination, sender, memInPos, memInLen, memOutPos,
memOutLen, flags) = callParams(k.cpt)
let (memOffset, memLength) =
if calcMemSize(memInPos, memInLen) > calcMemSize(memOutPos, memOutLen):
(memInPos, memInLen)
else:
(memOutPos, memOutLen)
# EIP2929
# This came before old gas calculator
# because it will affect `k.cpt.gasMeter.gasRemaining`
# and further `childGasLimit`
if k.cpt.fork >= FkBerlin:
k.cpt.vmState.mutateStateDB:
if not db.inAccessList(destination):
db.accessList(destination)
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
k.cpt.gasMeter.consumeGas(
ColdAccountAccessCost - WarmStorageReadCost,
reason = "EIP2929 gasCall")
let contractAddress = destination
var (gasCost, childGasLimit) = k.cpt.gasCosts[Call].c_handler(
value,
GasParams(
kind: Call,
c_isNewAccount: not k.cpt.accountExists(contractAddress),
c_gasBalance: k.cpt.gasMeter.gasRemaining,
c_contractGas: gas,
c_currentMemSize: k.cpt.memory.len,
c_memOffset: memOffset,
c_memLength: memLength))
# EIP 2046: temporary disabled
# reduce gas fee for precompiles
# from 700 to 40
if gasCost >= 0:
k.cpt.gasMeter.consumeGas(gasCost, reason = $Call)
k.cpt.returnData.setLen(0)
if k.cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = k.cpt.msg.depth
k.cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (call)")
k.cpt.memory.extend(memInPos, memInLen)
k.cpt.memory.extend(memOutPos, memOutLen)
let senderBalance = k.cpt.getBalance(sender)
if senderBalance < value:
debug "Insufficient funds",
available = senderBalance,
needed = k.cpt.msg.value
k.cpt.gasMeter.returnGas(childGasLimit)
return
let msg = Message(
kind: evmcCall,
depth: k.cpt.msg.depth + 1,
gas: childGasLimit,
sender: sender,
contractAddress: contractAddress,
codeAddress: destination,
value: value,
data: k.cpt.memory.read(memInPos, memInLen),
flags: flags)
var child = newComputation(k.cpt.vmState, msg)
k.cpt.chainTo(child):
if not child.shouldBurnGas:
k.cpt.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
k.cpt.merge(child)
k.cpt.stack.top(1)
k.cpt.returnData = child.output
let actualOutputSize = min(memOutLen, child.output.len)
if actualOutputSize > 0:
k.cpt.memory.write(memOutPos,
child.output.toOpenArray(0, actualOutputSize - 1))
# ---------------------
callCodeOp: Vm2OpFn = proc(k: Vm2Ctx) =
## 0xf2, Message-call into this account with an alternative account's code.
let (gas, value, destination, sender, memInPos, memInLen, memOutPos,
memOutLen, flags) = callCodeParams(k.cpt)
let (memOffset, memLength) =
if calcMemSize(memInPos, memInLen) > calcMemSize(memOutPos, memOutLen):
(memInPos, memInLen)
else:
(memOutPos, memOutLen)
# EIP2929
# This came before old gas calculator
# because it will affect `k.cpt.gasMeter.gasRemaining`
# and further `childGasLimit`
if k.cpt.fork >= FkBerlin:
k.cpt.vmState.mutateStateDB:
if not db.inAccessList(destination):
db.accessList(destination)
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
k.cpt.gasMeter.consumeGas(
ColdAccountAccessCost - WarmStorageReadCost,
reason = "EIP2929 gasCall")
let contractAddress = k.cpt.msg.contractAddress
var (gasCost, childGasLimit) = k.cpt.gasCosts[CallCode].c_handler(
value,
GasParams(
kind: CallCode,
c_isNewAccount: not k.cpt.accountExists(contractAddress),
c_gasBalance: k.cpt.gasMeter.gasRemaining,
c_contractGas: gas,
c_currentMemSize: k.cpt.memory.len,
c_memOffset: memOffset,
c_memLength: memLength))
# EIP 2046: temporary disabled
# reduce gas fee for precompiles
# from 700 to 40
if gasCost >= 0:
k.cpt.gasMeter.consumeGas(gasCost, reason = $CallCode)
k.cpt.returnData.setLen(0)
if k.cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = k.cpt.msg.depth
k.cpt.gasMeter.returnGas(childGasLimit)
return
# EIP 2046: temporary disabled
# reduce gas fee for precompiles
# from 700 to 40
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (callCode)")
k.cpt.memory.extend(memInPos, memInLen)
k.cpt.memory.extend(memOutPos, memOutLen)
let senderBalance = k.cpt.getBalance(sender)
if senderBalance < value:
debug "Insufficient funds",
available = senderBalance,
needed = k.cpt.msg.value
k.cpt.gasMeter.returnGas(childGasLimit)
return
let msg = Message(
kind: evmcCallCode,
depth: k.cpt.msg.depth + 1,
gas: childGasLimit,
sender: sender,
contractAddress: contractAddress,
codeAddress: destination,
value: value,
data: k.cpt.memory.read(memInPos, memInLen),
flags: flags)
var child = newComputation(k.cpt.vmState, msg)
k.cpt.chainTo(child):
if not child.shouldBurnGas:
k.cpt.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
k.cpt.merge(child)
k.cpt.stack.top(1)
k.cpt.returnData = child.output
let actualOutputSize = min(memOutLen, child.output.len)
if actualOutputSize > 0:
k.cpt.memory.write(memOutPos,
child.output.toOpenArray(0, actualOutputSize - 1))
# ---------------------
delegateCallOp: Vm2OpFn = proc(k: Vm2Ctx) =
## 0xf4, Message-call into this account with an alternative account's
## code, but persisting the current values for sender and value.
let (gas, value, destination, sender, memInPos, memInLen, memOutPos,
memOutLen, flags) = delegateCallParams(k.cpt)
let (memOffset, memLength) =
if calcMemSize(memInPos, memInLen) > calcMemSize(memOutPos, memOutLen):
(memInPos, memInLen)
else:
(memOutPos, memOutLen)
# EIP2929
# This came before old gas calculator
# because it will affect `k.cpt.gasMeter.gasRemaining`
# and further `childGasLimit`
if k.cpt.fork >= FkBerlin:
k.cpt.vmState.mutateStateDB:
if not db.inAccessList(destination):
db.accessList(destination)
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
k.cpt.gasMeter.consumeGas(
ColdAccountAccessCost - WarmStorageReadCost,
reason = "EIP2929 gasCall")
let contractAddress = k.cpt.msg.contractAddress
var (gasCost, childGasLimit) = k.cpt.gasCosts[DelegateCall].c_handler(
value,
GasParams(
kind: DelegateCall,
c_isNewAccount: not k.cpt.accountExists(contractAddress),
c_gasBalance: k.cpt.gasMeter.gasRemaining,
c_contractGas: gas,
c_currentMemSize: k.cpt.memory.len,
c_memOffset: memOffset,
c_memLength: memLength))
# EIP 2046: temporary disabled
# reduce gas fee for precompiles
# from 700 to 40
if gasCost >= 0:
k.cpt.gasMeter.consumeGas(gasCost, reason = $DelegateCall)
k.cpt.returnData.setLen(0)
if k.cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = k.cpt.msg.depth
k.cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (delegateCall)")
k.cpt.memory.extend(memInPos, memInLen)
k.cpt.memory.extend(memOutPos, memOutLen)
let msg = Message(
kind: evmcDelegateCall,
depth: k.cpt.msg.depth + 1,
gas: childGasLimit,
sender: sender,
contractAddress: contractAddress,
codeAddress: destination,
value: value,
data: k.cpt.memory.read(memInPos, memInLen),
flags: flags)
var child = newComputation(k.cpt.vmState, msg)
k.cpt.chainTo(child):
if not child.shouldBurnGas:
k.cpt.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
k.cpt.merge(child)
k.cpt.stack.top(1)
k.cpt.returnData = child.output
let actualOutputSize = min(memOutLen, child.output.len)
if actualOutputSize > 0:
k.cpt.memory.write(memOutPos,
child.output.toOpenArray(0, actualOutputSize - 1))
# ---------------------
staticCallOp: Vm2OpFn = proc(k: Vm2Ctx) =
## 0xfa, Static message-call into an account.
let (gas, value, destination, sender, memInPos, memInLen, memOutPos,
memOutLen, flags) = staticCallParams(k.cpt)
let (memOffset, memLength) =
if calcMemSize(memInPos, memInLen) > calcMemSize(memOutPos, memOutLen):
(memInPos, memInLen)
else:
(memOutPos, memOutLen)
# EIP2929
# This came before old gas calculator
# because it will affect `k.cpt.gasMeter.gasRemaining`
# and further `childGasLimit`
if k.cpt.fork >= FkBerlin:
if k.cpt.fork >= FkBerlin:
k.cpt.vmState.mutateStateDB:
if not db.inAccessList(destination):
db.accessList(destination)
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
k.cpt.gasMeter.consumeGas(
ColdAccountAccessCost - WarmStorageReadCost,
reason = "EIP2929 gasCall")
let contractAddress = destination
var (gasCost, childGasLimit) = k.cpt.gasCosts[StaticCall].c_handler(
value,
GasParams(
kind: StaticCall,
c_isNewAccount: not k.cpt.accountExists(contractAddress),
c_gasBalance: k.cpt.gasMeter.gasRemaining,
c_contractGas: gas,
c_currentMemSize: k.cpt.memory.len,
c_memOffset: memOffset,
c_memLength: memLength))
# EIP 2046: temporary disabled
# reduce gas fee for precompiles
# from 700 to 40
#
# when opCode == StaticCall:
# if k.cpt.fork >= FkBerlin and destination.toInt <= MaxPrecompilesAddr:
# gasCost = gasCost - 660.GasInt
if gasCost >= 0:
k.cpt.gasMeter.consumeGas(gasCost, reason = $StaticCall)
k.cpt.returnData.setLen(0)
if k.cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = k.cpt.msg.depth
k.cpt.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (staticCall)")
k.cpt.memory.extend(memInPos, memInLen)
k.cpt.memory.extend(memOutPos, memOutLen)
let msg = Message(
kind: evmcCall,
depth: k.cpt.msg.depth + 1,
gas: childGasLimit,
sender: sender,
contractAddress: contractAddress,
codeAddress: destination,
value: value,
data: k.cpt.memory.read(memInPos, memInLen),
flags: flags)
var child = newComputation(k.cpt.vmState, msg)
k.cpt.chainTo(child):
if not child.shouldBurnGas:
k.cpt.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
k.cpt.merge(child)
k.cpt.stack.top(1)
k.cpt.returnData = child.output
let actualOutputSize = min(memOutLen, child.output.len)
if actualOutputSize > 0:
k.cpt.memory.write(memOutPos,
child.output.toOpenArray(0, actualOutputSize - 1))
# ------------------------------------------------------------------------------
# Public, op exec table entries
# ------------------------------------------------------------------------------
const
vm2OpExecCall*: seq[Vm2OpExec] = @[
(opCode: Call, ## 0xf1, Message-Call into an account
forks: Vm2OpAllForks,
info: "Message-Call into an account",
exec: (prep: vm2OpIgnore,
run: callOp,
post: vm2OpIgnore)),
(opCode: CallCode, ## 0xf2, Message-Call with alternative code
forks: Vm2OpAllForks,
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: Vm2OpAllForks,
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: Vm2OpAllForks,
info: "Static message-call into an account",
exec: (prep: vm2OpIgnore,
run: staticCallOp,
post: vm2OpIgnore))]
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -57,6 +57,7 @@ else:
depth*: int
gas*: GasInt
contractAddress*: EthAddress
codeAddress*: EthAddress
sender*: EthAddress
value*: UInt256
data*: seq[byte]

View File

@ -6,44 +6,12 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
strformat, times, sets, options,
chronicles, stint, nimcrypto, eth/common,
./utils/[macros_procs_opcodes, v2utils_numeric],
./gas_meter, ./v2gas_costs, ./v2opcode_values, ./v2forks,
../v2memory, ../stack, ../code_stream, ../v2computation, ../v2state, ../v2types,
../../errors, ../../constants,
../../db/[db_chain, accounts_cache]
# verify that experimental op table compiles
import
./op_handlers, ./op_handlers/oph_defs
logScope:
topics = "opcode impl"
# ##################################
# Syntactic sugar
template push(x: typed) {.dirty.} =
## Push an expression on the computation stack
c.stack.push x
./op_handlers/oph_defs,
./op_handlers
# ##################################
# re-implemented OP handlers
var gdbBPHook_counter = 0
proc gdbBPHook*() =
gdbBPHook_counter.inc
stderr.write &"*** Hello {gdbBPHook_counter}\n"
stderr.flushFile
template opHandlerX(callName: untyped; opCode: Op; fork = FkBerlin) =
proc callName*(c: Computation) =
gdbBPHook()
var desc: Vm2Ctx
desc.cpt = c
vm2OpHandlers[fork][opCode].exec.run(desc)
template opHandler(callName: untyped; opCode: Op; fork = FkBerlin) =
proc callName*(c: Computation) =
var desc: Vm2Ctx
@ -203,8 +171,12 @@ opHandler log2, Op.Log2
opHandler log3, Op.Log3
opHandler log4, Op.Log4
opHandler create, Op.Create
opHandler call, Op.Call
opHandler callCode, Op.CallCode
opHandler create2, Op.Create2
opHandler returnOp, Op.Return
opHandler delegateCall, Op.DelegateCall
opHandler staticCall, Op.StaticCall
opHandler revert, Op.Revert
opHandler invalidOp, Op.Invalid
@ -212,175 +184,3 @@ opHandler selfDestruct, Op.SelfDestruct, FkFrontier
opHandler selfDestructEIP150, Op.SelfDestruct, FkTangerine
opHandler selfDestructEIP161, Op.SelfDestruct, FkSpurious
opHandler selfDestructEIP2929, Op.SelfDestruct
# ##########################################
# f0s: System operations.
proc callParams(c: Computation): (UInt256, UInt256, EthAddress, EthAddress, CallKind, int, int, int, int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
let value = c.stack.popInt()
result = (gas,
value,
destination,
c.msg.contractAddress, # sender
evmcCall,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.msg.flags)
proc callCodeParams(c: Computation): (UInt256, UInt256, EthAddress, EthAddress, CallKind, int, int, int, int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
let value = c.stack.popInt()
result = (gas,
value,
destination,
c.msg.contractAddress, # sender
evmcCallCode,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.msg.flags)
proc delegateCallParams(c: Computation): (UInt256, UInt256, EthAddress, EthAddress, CallKind, int, int, int, int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
result = (gas,
c.msg.value, # value
destination,
c.msg.sender, # sender
evmcDelegateCall,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.msg.flags)
proc staticCallParams(c: Computation): (UInt256, UInt256, EthAddress, EthAddress, CallKind, int, int, int, int, MsgFlags) =
let gas = c.stack.popInt()
let destination = c.stack.popAddress()
result = (gas,
0.u256, # value
destination,
c.msg.contractAddress, # sender
evmcCall,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
c.stack.popInt().cleanMemRef,
emvcStatic) # is_static
template genCall(callName: untyped, opCode: Op): untyped =
op callName, inline = false:
## CALL, 0xf1, Message-Call into an account
## CALLCODE, 0xf2, Message-call into this account with an alternative account's code.
## DELEGATECALL, 0xf4, Message-call into this account with an alternative account's code, but persisting the current values for sender and value.
## STATICCALL, 0xfa, Static message-call into an account.
when opCode == Call:
if emvcStatic == c.msg.flags and c.stack[^3, Uint256] > 0.u256:
raise newException(StaticContextError, "Cannot modify state while inside of a STATICCALL context")
let (gas, value, destination, sender, callKind,
memInPos, memInLen, memOutPos, memOutLen, flags) = `callName Params`(c)
push: 0
let (memOffset, memLength) = if calcMemSize(memInPos, memInLen) > calcMemSize(memOutPos, memOutLen):
(memInPos, memInLen)
else:
(memOutPos, memOutLen)
# EIP2929
# This came before old gas calculator
# because it will affect `c.gasMeter.gasRemaining`
# and further `childGasLimit`
if c.fork >= FkBerlin:
c.vmState.mutateStateDB:
if not db.inAccessList(destination):
db.accessList(destination)
# The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant `gasCall`
c.gasMeter.consumeGas(ColdAccountAccessCost - WarmStorageReadCost, reason = "EIP2929 gasCall")
let contractAddress = when opCode in {Call, StaticCall}: destination else: c.msg.contractAddress
var (gasCost, childGasLimit) = c.gasCosts[opCode].c_handler(
value,
GasParams(kind: opCode,
c_isNewAccount: not c.accountExists(contractAddress),
c_gasBalance: c.gasMeter.gasRemaining,
c_contractGas: gas,
c_currentMemSize: c.memory.len,
c_memOffset: memOffset,
c_memLength: memLength
))
# EIP 2046: temporary disabled
# reduce gas fee for precompiles
# from 700 to 40
#when opCode == StaticCall:
# if c.fork >= FkBerlin and destination.toInt <= MaxPrecompilesAddr:
# gasCost = gasCost - 660.GasInt
if gasCost >= 0:
c.gasMeter.consumeGas(gasCost, reason = $opCode)
c.returnData.setLen(0)
if c.msg.depth >= MaxCallDepth:
debug "Computation Failure", reason = "Stack too deep", maximumDepth = MaxCallDepth, depth = c.msg.depth
# return unused gas
c.gasMeter.returnGas(childGasLimit)
return
if gasCost < 0 and childGasLimit <= 0:
raise newException(OutOfGas, "Gas not enough to perform calculation (" & callName.astToStr & ")")
c.memory.extend(memInPos, memInLen)
c.memory.extend(memOutPos, memOutLen)
when opCode in {CallCode, Call}:
let senderBalance = c.getBalance(sender)
if senderBalance < value:
debug "Insufficient funds", available = senderBalance, needed = c.msg.value
# return unused gas
c.gasMeter.returnGas(childGasLimit)
return
block:
let msg = Message(
kind: callKind,
depth: c.msg.depth + 1,
gas: childGasLimit,
sender: sender,
contractAddress: contractAddress,
codeAddress: destination,
value: value,
data: c.memory.read(memInPos, memInLen),
flags: flags)
var child = newComputation(c.vmState, msg)
c.chainTo(child):
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(memOutLen, child.output.len)
if actualOutputSize > 0:
c.memory.write(memOutPos,
child.output.toOpenArray(0, actualOutputSize - 1))
genCall(call, Call)
genCall(callCode, CallCode)
genCall(delegateCall, DelegateCall)
genCall(staticCall, StaticCall)

View File

@ -1,137 +0,0 @@
# Nimbus
# Copyright (c) 2018 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.
# ##################################################################
# Macros to facilitate opcode procs creation
import
macros, strformat, stint, eth/common,
../../v2computation, ../../stack, ../../code_stream, ../../v2memory,
../../v2types, ../../../errors, ../gas_meter, ../v2opcode_values,
./v2utils_numeric
proc pop(tree: var NimNode): NimNode =
## Returns the last value of a NimNode and remove it
result = tree[tree.len-1]
tree.del(tree.len-1)
macro op*(procname: untyped, inline: static[bool], stackParams_body: varargs[untyped]): untyped =
## Usage:
## .. code-block:: nim
## op add, inline = true, lhs, rhs:
## push:
## lhs + rhs
# TODO: Unfortunately due to varargs[untyped] consuming all following parameters,
# we can't have a nicer macro signature `stackParams: varargs[untyped], body: untyped`
# see https://github.com/nim-lang/Nim/issues/5855 and are forced to "pop"
let computation = newIdentNode("c")
var stackParams = stackParams_body
# 1. Separate stackParams and body with pop
let body = newStmtList().add stackParams.pop
# 3. let (x, y, z) = computation.stack.popInt(3)
let len = stackParams.len
var popStackStmt = nnkVarTuple.newTree()
if len != 0:
for params in stackParams:
popStackStmt.add newIdentNode(params.strVal)
popStackStmt.add newEmptyNode()
popStackStmt.add quote do:
`computation`.stack.popInt(`len`)
popStackStmt = nnkStmtList.newTree(
nnkLetSection.newTree(popStackStmt)
)
else:
popStackStmt = nnkDiscardStmt.newTree(newEmptyNode())
# 4. Generate the proc
# TODO: replace by func to ensure no side effects
if inline:
result = quote do:
proc `procname`*(`computation`: Computation) {.inline.} =
`popStackStmt`
`body`
else:
result = quote do:
proc `procname`*(`computation`: Computation) {.gcsafe.} =
`popStackStmt`
`body`
macro genPush*(): untyped =
# TODO: avoid allocating a seq[byte], transforming to a string, stripping char
func genName(size: int): NimNode = ident(&"push{size}")
result = newStmtList()
for size in 1 .. 32:
let name = genName(size)
result.add quote do:
func `name`*(computation: Computation) {.inline.}=
## Push `size`-byte(s) on the stack
computation.stack.push computation.code.readVmWord(`size`)
macro genDup*(): untyped =
func genName(position: int): NimNode = ident(&"dup{position}")
result = newStmtList()
for pos in 1 .. 16:
let name = genName(pos)
result.add quote do:
func `name`*(computation: Computation) {.inline.}=
computation.stack.dup(`pos`)
macro genSwap*(): untyped =
func genName(position: int): NimNode = ident(&"swap{position}")
result = newStmtList()
for pos in 1 .. 16:
let name = genName(pos)
result.add quote do:
func `name`*(computation: Computation) {.inline.}=
computation.stack.swap(`pos`)
template checkInStaticContext*(comp: Computation) =
# TODO: if possible, this check only appear
# when fork >= FkByzantium
if emvcStatic == comp.msg.flags:
raise newException(StaticContextError, "Cannot modify state while inside of a STATICCALL context")
proc logImpl(c: Computation, opcode: Op, topicCount: int) =
doAssert(topicCount in 0 .. 4)
checkInStaticContext(c)
let (memStartPosition, size) = c.stack.popInt(2)
let (memPos, len) = (memStartPosition.cleanMemRef, size.cleanMemRef)
if memPos < 0 or len < 0:
raise newException(OutOfBoundsRead, "Out of bounds memory access")
c.gasMeter.consumeGas(
c.gasCosts[opcode].m_handler(c.memory.len, memPos, len),
reason="Memory expansion, Log topic and data gas cost")
c.memory.extend(memPos, len)
block:
var log: Log
log.topics = newSeqOfCap[Topic](topicCount)
for i in 0 ..< topicCount:
log.topics.add(c.stack.popTopic())
log.data = c.memory.read(memPos, len)
log.address = c.msg.contractAddress
c.addLogEntry(log)
template genLog*() =
proc log0*(c: Computation) {.inline.} = logImpl(c, Log0, 0)
proc log1*(c: Computation) {.inline.} = logImpl(c, Log1, 1)
proc log2*(c: Computation) {.inline.} = logImpl(c, Log2, 2)
proc log3*(c: Computation) {.inline.} = logImpl(c, Log3, 3)
proc log4*(c: Computation) {.inline.} = logImpl(c, Log4, 4)