Remove exceptions from EVM (#2314)

* Remove exception from evm memory

* Remove exception from gas meter

* Remove exception from stack

* Remove exception from precompiles

* Remove exception from gas_costs

* Remove exception from op handlers

* Remove exception from op dispatcher

* Remove exception from call_evm

* Remove exception from EVM

* Fix tools and tests

* Remove exception from EVMC

* fix evmc

* Fix evmc

* Remove remnants of async evm stuff

* Remove superflous error handling

* Proc to func

* Fix errors detected by CI

* Fix EVM op call stack usage

* REmove exception handling from getVmState

* Better error message instead of just doAssert

* Remove unused validation

* Remove superflous catchRaise

* Use results.expect instead of unsafeValue
This commit is contained in:
andri lim 2024-06-07 15:24:32 +07:00 committed by GitHub
parent dd8095753a
commit b3a5c67532
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 1796 additions and 1837 deletions

View File

@ -55,13 +55,8 @@ const
proc getVmState(c: ChainRef, header: BlockHeader):
Result[BaseVMState, string] =
let vmState = BaseVMState()
try:
# TODO clean up exception handling
if not vmState.init(header, c.com):
return err("Could not initialise VMState")
except CatchableError as exc:
return err("Error while initializing VMState: " & exc.msg)
if not vmState.init(header, c.com):
return err("Could not initialise VMState")
ok(vmState)
proc purgeOlderBlocksFromHistory(
@ -141,7 +136,8 @@ proc persistBlocksImpl(c: ChainRef; headers: openArray[BlockHeader];
let
mkeys = vmState.stateDB.makeMultiKeys()
# Reset state to what it was before executing the block of transactions
initialState = BaseVMState.new(header, c.com)
initialState = BaseVMState.new(header, c.com).valueOr:
return err("Failed to create vm state")
witness = initialState.buildWitness(mkeys)
dbTx.rollback()

View File

@ -31,8 +31,7 @@ proc processTransactions*(
vmState: BaseVMState;
header: BlockHeader;
transactions: seq[Transaction];
): Result[void, string]
{.gcsafe, raises: [CatchableError].} =
): Result[void, string] =
vmState.receipts = newSeq[Receipt](transactions.len)
vmState.cumulativeGasUsed = 0

View File

@ -70,8 +70,7 @@ proc processTransactionImpl(
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork;
): Result[GasInt, string]
{.raises: [CatchableError].} =
): Result[GasInt, string] =
## Modelled after `https://eips.ethereum.org/EIPS/eip-1559#specification`_
## which provides a backward compatible framwork for EIP1559.
@ -127,7 +126,7 @@ proc processTransactionImpl(
# ------------------------------------------------------------------------------
proc processBeaconBlockRoot*(vmState: BaseVMState, beaconRoot: Hash256):
Result[void, string] {.raises: [CatchableError].} =
Result[void, string] =
## processBeaconBlockRoot applies the EIP-4788 system call to the
## beacon block root contract. This method is exported to be used in tests.
## If EIP-4788 is enabled, we need to invoke the beaconroot storage
@ -164,8 +163,7 @@ proc processTransaction*(
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork;
): Result[GasInt,string]
{.raises: [CatchableError].} =
): Result[GasInt,string] =
vmState.processTransactionImpl(tx, sender, header, fork)
proc processTransaction*(
@ -173,8 +171,7 @@ proc processTransaction*(
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader;
): Result[GasInt,string]
{.raises: [CatchableError].} =
): Result[GasInt,string] =
let fork = vmState.com.toEVMFork(header.forkDeterminationInfo)
vmState.processTransaction(tx, sender, header, fork)

View File

@ -78,8 +78,7 @@ proc persist(pst: TxPackerStateRef)
# Private functions
# ------------------------------------------------------------------------------
proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt
{.gcsafe,raises: [CatchableError].} =
proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt =
## Execute item transaction and update `vmState` book keeping. Returns the
## `gasUsed` after executing the transaction.
let
@ -87,12 +86,10 @@ proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt
baseFee = pst.xp.chain.baseFee
tx = item.tx.eip1559TxNormalization(baseFee.GasInt)
#safeExecutor "tx_packer.runTx":
# # Execute transaction, may return a wildcard `Exception`
result = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
let gasUsed = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
pst.cleanState = false
doAssert 0 <= result
doAssert 0 <= gasUsed
gasUsed
proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt)
{.gcsafe,raises: [CatchableError].} =

View File

@ -15,6 +15,7 @@ import
"."/[code_stream, memory, message, stack, state],
"."/[types],
./interpreter/[gas_meter, gas_costs, op_codes],
./evm_errors,
../common/[common, evmforks],
../utils/utils,
stew/byteutils,
@ -131,25 +132,24 @@ template getBlobBaseFee*(c: Computation): UInt256 =
else:
c.vmState.txCtx.blobBaseFee
proc getBlockHash*(c: Computation, number: UInt256): Hash256
{.gcsafe, raises: [CatchableError].} =
proc getBlockHash*(c: Computation, number: UInt256): Hash256 =
when evmc_enabled:
let
blockNumber = c.host.getTxContext().block_number.u256
ancestorDepth = blockNumber - number - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
return Hash256()
if number >= blockNumber:
return
return Hash256()
c.host.getBlockHash(number)
else:
let
blockNumber = c.vmState.blockNumber
ancestorDepth = blockNumber - number - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
return Hash256()
if number >= blockNumber:
return
return Hash256()
c.vmState.getAncestorHash(number.vmWordToBlockNumber)
template accountExists*(c: Computation, address: EthAddress): bool =
@ -221,8 +221,8 @@ proc newComputation*(vmState: BaseVMState, sysCall: bool, message: Message,
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.memory = EvmMemoryRef.new()
result.stack = EvmStackRef.new()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.sysCall = sysCall
@ -240,8 +240,8 @@ func newComputation*(vmState: BaseVMState, sysCall: bool,
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.memory = EvmMemoryRef.new()
result.stack = EvmStackRef.new()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.code = newCodeStream(code)
@ -298,8 +298,7 @@ func errorOpt*(c: Computation): Option[string] =
return none(string)
some(c.error.info)
proc writeContract*(c: Computation)
{.gcsafe, raises: [CatchableError].} =
proc writeContract*(c: Computation) =
template withExtra(tracer: untyped, args: varargs[untyped]) =
tracer args, newContract=($c.msg.contractAddress),
blockNumber=c.vmState.blockNumber,
@ -332,10 +331,15 @@ proc writeContract*(c: Computation)
# gas difference matters. The new code can be called later in the
# transaction too, before self-destruction wipes the account at the end.
let gasParams = GasParams(kind: Create, cr_memLength: len)
let codeCost = c.gasCosts[Create].c_handler(0.u256, gasParams).gasCost
let
gasParams = GasParams(kind: Create, cr_memLength: len)
res = c.gasCosts[Create].cr_handler(0.u256, gasParams)
codeCost = res.gasCost
if codeCost <= c.gasMeter.gasRemaining:
c.gasMeter.consumeGas(codeCost, reason = "Write new contract code")
c.gasMeter.consumeGas(codeCost,
reason = "Write new contract code").
expect("enough gas since we checked against gasRemaining")
c.vmState.mutateStateDB:
db.setCode(c.msg.contractAddress, c.output)
withExtra trace, "Writing new contract code"
@ -354,48 +358,17 @@ proc writeContract*(c: Computation)
template chainTo*(c: Computation,
toChild: typeof(c.child),
shouldRaise: static[bool],
after: untyped) =
when shouldRaise:
{.pragma: chainToPragma, gcsafe, raises: [CatchableError].}
else:
{.pragma: chainToPragma, gcsafe, raises: [].}
c.child = toChild
c.continuation = proc() {.chainToPragma.} =
c.continuation = nil
after
# Register an async operation to be performed before the continuation is called.
template asyncChainTo*(c: Computation,
asyncOperation: Future[void],
after: untyped) =
c.pendingAsyncOperation = asyncOperation
c.continuation = proc() {.gcsafe, raises: [].} =
c.continuation = nil
after
template asyncChainToRaise*(c: Computation,
asyncOperation: Future[void],
RaisesTypes: untyped,
after: untyped) =
c.pendingAsyncOperation = asyncOperation
c.continuation = proc() {.gcsafe, raises: RaisesTypes.} =
c.continuation = proc(): EvmResultVoid {.gcsafe, raises: [].} =
c.continuation = nil
after
func merge*(c, child: Computation) =
c.gasMeter.refundGas(child.gasMeter.gasRefunded)
when evmc_enabled:
{.pragma: selfDesructPragma, gcsafe, raises: [CatchableError].}
else:
{.pragma: selfDesructPragma, gcsafe, raises: [].}
proc execSelfDestruct*(c: Computation, beneficiary: EthAddress)
{.selfDesructPragma.} =
proc execSelfDestruct*(c: Computation, beneficiary: EthAddress) =
c.vmState.mutateStateDB:
let localBalance = c.getBalance(c.msg.contractAddress)
@ -468,8 +441,8 @@ func prepareTracer*(c: Computation) =
c.vmState.capturePrepare(c, c.msg.depth)
func opcodeGastCost*(
c: Computation, op: Op, gasCost: GasInt, reason: string)
{.raises: [OutOfGas, ValueError].} =
c: Computation, op: Op, gasCost: GasInt, reason: string): EvmResultVoid
{.raises: [].} =
c.vmState.captureGasCost(
c,
op,

73
nimbus/evm/evm_errors.nim Normal file
View File

@ -0,0 +1,73 @@
# Nimbus
# Copyright (c) 2018-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.
{.push raises: [].}
import
results
export
results
type
EvmErrorCode* {.pure.} = enum
EvmBug
OutOfGas
MemoryFull
StackFull
StackInsufficient
PrcInvalidSig
PrcInvalidPoint
PrcInvalidParam
PrcValidationError
GasIntOverflow
InvalidInstruction
StaticContext
InvalidJumpDest
OutOfBounds
InvalidInitCode
EvmHeaderNotFound
EvmInvalidParam
EvmErrorObj* = object
code*: EvmErrorCode
EvmResultVoid* = Result[void, EvmErrorObj]
EvmResult*[T] = Result[T, EvmErrorObj]
template gasErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)
template memErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)
template stackErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)
template prcErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)
template opErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)
template evmErr*(errCode): auto =
EvmErrorObj(
code: EvmErrorCode.errCode,
)

View File

@ -56,36 +56,36 @@ type
output_data* : ptr byte
output_size* : uint
release* : proc(result: var nimbus_result)
{.cdecl, gcsafe, raises: [CatchableError].}
{.cdecl, gcsafe, raises: [].}
create_address*: EthAddress
padding* : array[4, byte]
nimbus_host_interface* = object
account_exists*: proc(context: evmc_host_context, address: EthAddress): bool {.cdecl, gcsafe, raises: [CatchableError].}
get_storage*: proc(context: evmc_host_context, address: EthAddress, key: ptr evmc_uint256be): evmc_uint256be {.cdecl, gcsafe, raises: [CatchableError].}
account_exists*: proc(context: evmc_host_context, address: EthAddress): bool {.cdecl, gcsafe, raises: [].}
get_storage*: proc(context: evmc_host_context, address: EthAddress, key: ptr evmc_uint256be): evmc_uint256be {.cdecl, gcsafe, raises: [].}
set_storage*: proc(context: evmc_host_context, address: EthAddress,
key, value: ptr evmc_uint256be): evmc_storage_status {.cdecl, gcsafe, raises: [CatchableError].}
get_balance*: proc(context: evmc_host_context, address: EthAddress): evmc_uint256be {.cdecl, gcsafe, raises: [CatchableError].}
get_code_size*: proc(context: evmc_host_context, address: EthAddress): uint {.cdecl, gcsafe, raises: [CatchableError].}
get_code_hash*: proc(context: evmc_host_context, address: EthAddress): Hash256 {.cdecl, gcsafe, raises: [CatchableError].}
key, value: ptr evmc_uint256be): evmc_storage_status {.cdecl, gcsafe, raises: [].}
get_balance*: proc(context: evmc_host_context, address: EthAddress): evmc_uint256be {.cdecl, gcsafe, raises: [].}
get_code_size*: proc(context: evmc_host_context, address: EthAddress): uint {.cdecl, gcsafe, raises: [].}
get_code_hash*: proc(context: evmc_host_context, address: EthAddress): Hash256 {.cdecl, gcsafe, raises: [].}
copy_code*: proc(context: evmc_host_context, address: EthAddress,
code_offset: int, buffer_data: ptr byte,
buffer_size: int): int {.cdecl, gcsafe, raises: [CatchableError].}
selfdestruct*: proc(context: evmc_host_context, address, beneficiary: EthAddress) {.cdecl, gcsafe, raises: [CatchableError].}
call*: proc(context: evmc_host_context, msg: ptr nimbus_message): nimbus_result {.cdecl, gcsafe, raises: [CatchableError].}
get_tx_context*: proc(context: evmc_host_context): nimbus_tx_context {.cdecl, gcsafe, raises: [CatchableError].}
get_block_hash*: proc(context: evmc_host_context, number: int64): Hash256 {.cdecl, gcsafe, raises: [CatchableError].}
buffer_size: int): int {.cdecl, gcsafe, raises: [].}
selfdestruct*: proc(context: evmc_host_context, address, beneficiary: EthAddress) {.cdecl, gcsafe, raises: [].}
call*: proc(context: evmc_host_context, msg: ptr nimbus_message): nimbus_result {.cdecl, gcsafe, raises: [].}
get_tx_context*: proc(context: evmc_host_context): nimbus_tx_context {.cdecl, gcsafe, raises: [].}
get_block_hash*: proc(context: evmc_host_context, number: int64): Hash256 {.cdecl, gcsafe, raises: [].}
emit_log*: proc(context: evmc_host_context, address: EthAddress,
data: ptr byte, data_size: uint,
topics: ptr evmc_bytes32, topics_count: uint) {.cdecl, gcsafe, raises: [CatchableError].}
topics: ptr evmc_bytes32, topics_count: uint) {.cdecl, gcsafe, raises: [].}
access_account*: proc(context: evmc_host_context,
address: EthAddress): evmc_access_status {.cdecl, gcsafe, raises: [CatchableError].}
address: EthAddress): evmc_access_status {.cdecl, gcsafe, raises: [].}
access_storage*: proc(context: evmc_host_context, address: EthAddress,
key: var evmc_bytes32): evmc_access_status {.cdecl, gcsafe, raises: [CatchableError].}
key: var evmc_bytes32): evmc_access_status {.cdecl, gcsafe, raises: [].}
get_transient_storage*: proc(context: evmc_host_context, address: EthAddress,
key: ptr evmc_uint256be): evmc_uint256be {.cdecl, gcsafe, raises: [CatchableError].}
key: ptr evmc_uint256be): evmc_uint256be {.cdecl, gcsafe, raises: [].}
set_transient_storage*: proc(context: evmc_host_context, address: EthAddress,
key, value: ptr evmc_uint256be) {.cdecl, gcsafe, raises: [CatchableError].}
key, value: ptr evmc_uint256be) {.cdecl, gcsafe, raises: [].}
proc nim_host_get_interface*(): ptr nimbus_host_interface {.importc, cdecl.}
proc nim_host_create_context*(vmstate: pointer, msg: ptr evmc_message): evmc_host_context {.importc, cdecl.}
@ -104,45 +104,36 @@ proc init*(x: var HostContext, host: ptr nimbus_host_interface, context: evmc_ho
proc init*(x: typedesc[HostContext], host: ptr nimbus_host_interface, context: evmc_host_context): HostContext =
result.init(host, context)
proc getTxContext*(ctx: HostContext): nimbus_tx_context
{.gcsafe, raises: [CatchableError].} =
proc getTxContext*(ctx: HostContext): nimbus_tx_context =
ctx.host.get_tx_context(ctx.context)
proc getBlockHash*(ctx: HostContext, number: UInt256): Hash256
{.gcsafe, raises: [CatchableError].} =
proc getBlockHash*(ctx: HostContext, number: UInt256): Hash256 =
ctx.host.get_block_hash(ctx.context, number.truncate(int64))
proc accountExists*(ctx: HostContext, address: EthAddress): bool
{.gcsafe, raises: [CatchableError].} =
proc accountExists*(ctx: HostContext, address: EthAddress): bool =
ctx.host.account_exists(ctx.context, address)
proc getStorage*(ctx: HostContext, address: EthAddress, key: UInt256): UInt256
{.gcsafe, raises: [CatchableError].} =
proc getStorage*(ctx: HostContext, address: EthAddress, key: UInt256): UInt256 =
var key = toEvmc(key)
UInt256.fromEvmc ctx.host.get_storage(ctx.context, address, key.addr)
proc setStorage*(ctx: HostContext, address: EthAddress,
key, value: UInt256): evmc_storage_status
{.gcsafe, raises: [CatchableError].} =
key, value: UInt256): evmc_storage_status =
var
key = toEvmc(key)
value = toEvmc(value)
ctx.host.set_storage(ctx.context, address, key.addr, value.addr)
proc getBalance*(ctx: HostContext, address: EthAddress): UInt256
{.gcsafe, raises: [CatchableError].} =
proc getBalance*(ctx: HostContext, address: EthAddress): UInt256 =
UInt256.fromEvmc ctx.host.get_balance(ctx.context, address)
proc getCodeSize*(ctx: HostContext, address: EthAddress): uint
{.gcsafe, raises: [CatchableError].} =
proc getCodeSize*(ctx: HostContext, address: EthAddress): uint =
ctx.host.get_code_size(ctx.context, address)
proc getCodeHash*(ctx: HostContext, address: EthAddress): Hash256
{.gcsafe, raises: [CatchableError].} =
proc getCodeHash*(ctx: HostContext, address: EthAddress): Hash256 =
ctx.host.get_code_hash(ctx.context, address)
proc copyCode*(ctx: HostContext, address: EthAddress, codeOffset: int = 0): seq[byte]
{.gcsafe, raises: [CatchableError].} =
proc copyCode*(ctx: HostContext, address: EthAddress, codeOffset: int = 0): seq[byte] =
let size = ctx.getCodeSize(address).int
if size - codeOffset > 0:
result = newSeq[byte](size - codeOffset)
@ -150,39 +141,32 @@ proc copyCode*(ctx: HostContext, address: EthAddress, codeOffset: int = 0): seq[
codeOffset, result[0].addr, result.len)
doAssert(read == result.len)
proc selfDestruct*(ctx: HostContext, address, beneficiary: EthAddress)
{.gcsafe, raises: [CatchableError].} =
proc selfDestruct*(ctx: HostContext, address, beneficiary: EthAddress) =
ctx.host.selfdestruct(ctx.context, address, beneficiary)
proc emitLog*(ctx: HostContext, address: EthAddress, data: openArray[byte],
topics: ptr evmc_bytes32, topicsCount: int)
{.gcsafe, raises: [CatchableError].} =
topics: ptr evmc_bytes32, topicsCount: int) =
ctx.host.emit_log(ctx.context, address, if data.len > 0: data[0].unsafeAddr else: nil,
data.len.uint, topics, topicsCount.uint)
proc call*(ctx: HostContext, msg: nimbus_message): nimbus_result
{.gcsafe, raises: [CatchableError].} =
proc call*(ctx: HostContext, msg: nimbus_message): nimbus_result =
ctx.host.call(ctx.context, msg.unsafeAddr)
proc accessAccount*(ctx: HostContext,
address: EthAddress): evmc_access_status
{.gcsafe, raises: [CatchableError].} =
address: EthAddress): evmc_access_status =
ctx.host.access_account(ctx.context, address)
proc accessStorage*(ctx: HostContext, address: EthAddress,
key: UInt256): evmc_access_status
{.gcsafe, raises: [CatchableError].} =
key: UInt256): evmc_access_status =
var key = toEvmc(key)
ctx.host.access_storage(ctx.context, address, key)
proc getTransientStorage*(ctx: HostContext, address: EthAddress, key: UInt256): UInt256
{.gcsafe, raises: [CatchableError].} =
proc getTransientStorage*(ctx: HostContext, address: EthAddress, key: UInt256): UInt256 =
var key = toEvmc(key)
UInt256.fromEvmc ctx.host.get_transient_storage(ctx.context, address, key.addr)
proc setTransientStorage*(ctx: HostContext, address: EthAddress,
key, value: UInt256)
{.gcsafe, raises: [CatchableError].} =
key, value: UInt256) =
var
key = toEvmc(key)
value = toEvmc(value)

View File

@ -8,7 +8,8 @@
import
math, eth/common/eth_types,
./utils/[macros_gen_opcodes, utils_numeric],
./op_codes, ../../common/evmforks, ../../errors
./op_codes, ../../common/evmforks,
../evm_errors
when defined(evmc_enabled):
import evmc/evmc
@ -94,6 +95,7 @@ type
GckFixed,
GckDynamic,
GckMemExpansion,
GckCreate,
GckComplex,
GckLater
@ -107,13 +109,16 @@ type
cost*: GasInt
of GckDynamic:
d_handler*: proc(value: UInt256): GasInt
{.nimcall, gcsafe, raises: [CatchableError].}
{.nimcall, gcsafe, raises: [].}
of GckMemExpansion:
m_handler*: proc(currentMemSize, memOffset, memLength: GasNatural): GasInt
{.nimcall, gcsafe, raises: [CatchableError].}
{.nimcall, gcsafe, raises: [].}
of GckCreate:
cr_handler*: proc(value: UInt256, gasParams: GasParams): GasResult
{.nimcall, gcsafe, raises: [].}
of GckComplex:
c_handler*: proc(value: UInt256, gasParams: GasParams): GasResult
{.nimcall, gcsafe, raises: [CatchableError].}
c_handler*: proc(value: UInt256, gasParams: GasParams): EvmResult[GasResult]
{.nimcall, gcsafe, raises: [].}
# We use gasCost/gasRefund for:
# - Properly log and order cost and refund (for Sstore especially)
# - Allow to use unsigned integer in the future
@ -310,13 +315,15 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
result = static(FeeSchedule[GasVeryLow])
result += `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
func `prefix gasSstore`(value: UInt256, gasParams: GasParams): GasResult {.nimcall.} =
func `prefix gasSstore`(value: UInt256, gasParams: GasParams): EvmResult[GasResult] {.nimcall.} =
## Value is word to save
var res: GasResult
when defined(evmc_enabled):
const c = SstoreCost[fork]
let sc = c[gasParams.s_status]
result.gasCost = sc.gasCost
result.gasRefund = sc.gasRefund
res.gasCost = sc.gasCost
res.gasRefund = sc.gasRefund
ok(res)
else:
when fork >= FkBerlin:
# EIP2929
@ -341,14 +348,14 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
let isStorageEmpty = gasParams.s_currentValue.isZero
# Gas cost - literal translation of Yellow Paper
result.gasCost = if value.isZero.not and isStorageEmpty:
res.gasCost = if value.isZero.not and isStorageEmpty:
InitGas
else:
CleanGas
# Refund
if value.isZero and not isStorageEmpty:
result.gasRefund = ClearRefund
res.gasRefund = ClearRefund
else:
# 0. If *gasleft* is less than or equal to 2300, fail the current call.
# 1. If current value equals new value (this is a no-op), SSTORE_NOOP_GAS gas is deducted.
@ -366,33 +373,34 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
# Gas sentry honoured, do the actual gas calculation based on the stored value
if gasParams.s_currentValue == value: # noop (1)
result.gasCost = NoopGas
return
res.gasCost = NoopGas
return ok(res)
if gasParams.s_originalValue == gasParams.s_currentValue:
if gasParams.s_originalValue.isZero: # create slot (2.1.1)
result.gasCost = InitGas
return
res.gasCost = InitGas
return ok(res)
if value.isZero: # delete slot (2.1.2b)
result.gasRefund = ClearRefund
res.gasRefund = ClearRefund
result.gasCost = CleanGas # write existing slot (2.1.2)
return
res.gasCost = CleanGas # write existing slot (2.1.2)
return ok(res)
if not gasParams.s_originalValue.isZero:
if gasParams.s_currentValue.isZero: # recreate slot (2.2.1.1)
result.gasRefund -= ClearRefund
res.gasRefund -= ClearRefund
if value.isZero: # delete slot (2.2.1.2)
result.gasRefund += ClearRefund
res.gasRefund += ClearRefund
if gasParams.s_originalValue == value:
if gasParams.s_originalValue.isZero: # reset to original inexistent slot (2.2.2.1)
result.gasRefund += InitRefund
res.gasRefund += InitRefund
else: # reset to original existing slot (2.2.2.2)
result.gasRefund += CleanRefund
res.gasRefund += CleanRefund
result.gasCost = DirtyGas # dirty update (2.2)
res.gasCost = DirtyGas # dirty update (2.2)
ok(res)
func `prefix gasLog0`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
@ -428,7 +436,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
static(FeeSchedule[GasLogData]) * memLength +
static(4 * FeeSchedule[GasLogTopic])
func `prefix gasCall`(value: UInt256, gasParams: GasParams): GasResult {.nimcall.} =
func `prefix gasCall`(value: UInt256, gasParams: GasParams): EvmResult[GasResult] {.nimcall.} =
# From the Yellow Paper, going through the equation from bottom to top
# https://ethereum.github.io/yellowpaper/paper.pdf#appendix.H
@ -469,7 +477,8 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
# - Go-Ethereum only has one cost
# https://github.com/ethereum/go-ethereum/blob/13af27641829f61d1e6b383e37aab6caae22f2c1/core/vm/gas_table.go#L334
# ⚠⚠ Py-EVM seems wrong if memory is needed for both in and out.
result.gasCost = `prefix gasMemoryExpansion`(
var res: GasResult
res.gasCost = `prefix gasMemoryExpansion`(
gasParams.c_currentMemSize,
gasParams.c_memOffset,
gasParams.c_memLength
@ -479,54 +488,57 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
if gasParams.c_isNewAccount and gasParams.kind == Call:
when fork < FkSpurious:
# Pre-EIP161 all account creation calls consumed 25000 gas.
result.gasCost += static(FeeSchedule[GasNewAccount])
res.gasCost += static(FeeSchedule[GasNewAccount])
else:
# Afterwards, only those transfering value:
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-158.md
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
if not value.isZero:
result.gasCost += static(FeeSchedule[GasNewAccount])
res.gasCost += static(FeeSchedule[GasNewAccount])
# Cxfer
if not value.isZero and gasParams.kind in {Call, CallCode}:
result.gasCost += static(FeeSchedule[GasCallValue])
res.gasCost += static(FeeSchedule[GasCallValue])
# Cextra
result.gasCost += static(FeeSchedule[GasCall])
res.gasCost += static(FeeSchedule[GasCall])
# Cgascap
when fork >= FkTangerine:
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
let gas = `prefix all_but_one_64th`(gasParams.c_gasBalance - result.gasCost)
let gas = `prefix all_but_one_64th`(gasParams.c_gasBalance - res.gasCost)
if gasParams.c_contractGas > high(GasInt).u256 or
gas < gasParams.c_contractGas.truncate(GasInt):
result.gasRefund = gas
res.gasRefund = gas
else:
result.gasRefund = gasParams.c_contractGas.truncate(GasInt)
res.gasRefund = gasParams.c_contractGas.truncate(GasInt)
else:
if gasParams.c_contractGas > high(GasInt).u256:
raise newException(TypeError, "GasInt Overflow (" & $gasParams.kind & ") " & $gasParams.c_contractGas)
result.gasRefund = gasParams.c_contractGas.truncate(GasInt)
return err(gasErr(GasIntOverflow))
res.gasRefund = gasParams.c_contractGas.truncate(GasInt)
if result.gasRefund > 0: # skip check if gasRefund is negative
if result.gasCost.u256 + result.gasRefund.u256 > high(GasInt).u256:
raise newException(TypeError, "GasInt overflow, gasCost=" &
$result.gasCost & ", gasRefund=" & $result.gasRefund)
if res.gasRefund > 0: # skip check if gasRefund is negative
if res.gasCost.u256 + res.gasRefund.u256 > high(GasInt).u256:
return err(gasErr(GasIntOverflow))
result.gasCost += result.gasRefund
res.gasCost += res.gasRefund
# Ccallgas - Gas sent to the child message
if not value.isZero and gasParams.kind in {Call, CallCode}:
result.gasRefund += static(FeeSchedule[GasCallStipend])
res.gasRefund += static(FeeSchedule[GasCallStipend])
ok(res)
func `prefix gasHalt`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
`prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
func `prefix gasSelfDestruct`(value: UInt256, gasParams: GasParams): GasResult {.nimcall.} =
result.gasCost += static(FeeSchedule[GasSelfDestruct])
func `prefix gasSelfDestruct`(value: UInt256, gasParams: GasParams): EvmResult[GasResult] {.nimcall.} =
var res: GasResult
res.gasCost += static(FeeSchedule[GasSelfDestruct])
when fork >= FkTangerine:
if gasParams.sd_condition:
result.gasCost += static(FeeSchedule[GasNewAccount])
res.gasCost += static(FeeSchedule[GasNewAccount])
ok(res)
func `prefix gasCreate2`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
result = static(FeeSchedule[GasSha3Word]) * (memLength).wordCount
@ -549,17 +561,21 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
GasCost(kind: GckLater, cost: static(FeeSchedule[gasFeeKind]))
func dynamic(handler: proc(value: UInt256): GasInt
{.nimcall, gcsafe, raises: [CatchableError].}): GasCost =
{.nimcall, gcsafe, raises: [].}): GasCost =
GasCost(kind: GckDynamic, d_handler: handler)
func memExpansion(handler: proc(currentMemSize, memOffset, memLength: GasNatural): GasInt
{.nimcall, gcsafe, raises: [CatchableError].}): GasCost =
{.nimcall, gcsafe, raises: [].}): GasCost =
GasCost(kind: GckMemExpansion, m_handler: handler)
func complex(handler: proc(value: UInt256, gasParams: GasParams): GasResult
{.nimcall, gcsafe, raises: [CatchableError].}): GasCost =
func complex(handler: proc(value: UInt256, gasParams: GasParams): EvmResult[GasResult]
{.nimcall, gcsafe, raises: [].}): GasCost =
GasCost(kind: GckComplex, c_handler: handler)
func handleCreate(handler: proc(value: UInt256, gasParams: GasParams): GasResult
{.nimcall, gcsafe, raises: [].}): GasCost =
GasCost(kind: GckCreate, cr_handler: handler)
# Returned value
fill_enum_table_holes(Op, GasCost(kind: GckInvalidOp)):
[
@ -727,7 +743,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
Log4: memExpansion `prefix gasLog4`,
# f0s: System operations
Create: complex `prefix gasCreate`,
Create: handleCreate `prefix gasCreate`,
Call: complex `prefix gasCall`,
CallCode: complex `prefix gasCall`,
Return: memExpansion `prefix gasHalt`,

View File

@ -1,32 +1,32 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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.
# * 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.
{.push raises: [].}
import
chronicles, strformat, eth/common, # GasInt
../../errors, ../types
eth/common, # GasInt
../evm_errors,
../types
logScope:
topics = "vm gas"
proc init*(m: var GasMeter, startGas: GasInt) =
func init*(m: var GasMeter, startGas: GasInt) =
m.gasRemaining = startGas
m.gasRefunded = 0
proc consumeGas*(gasMeter: var GasMeter; amount: GasInt; reason: string) =
func consumeGas*(gasMeter: var GasMeter; amount: GasInt; reason: string): EvmResultVoid =
if amount > gasMeter.gasRemaining:
raise newException(OutOfGas,
&"Out of gas: Needed {amount} - Remaining {gasMeter.gasRemaining} - Reason: {reason}")
return err(memErr(OutOfGas))
gasMeter.gasRemaining -= amount
#trace "GAS CONSUMPTION", total = gasMeter.gasRemaining + amount, amount, remaining = gasMeter.gasRemaining, reason
ok()
proc returnGas*(gasMeter: var GasMeter; amount: GasInt) =
func returnGas*(gasMeter: var GasMeter; amount: GasInt) =
gasMeter.gasRemaining += amount
#trace "GAS RETURNED", consumed = gasMeter.gasRemaining - amount, amount, remaining = gasMeter.gasRemaining
proc refundGas*(gasMeter: var GasMeter; amount: GasInt) =
func refundGas*(gasMeter: var GasMeter; amount: GasInt) =
gasMeter.gasRefunded += amount
#trace "GAS REFUND", consumed = gasMeter.gasRemaining - amount, amount, refunded = gasMeter.gasRefunded

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -17,6 +17,7 @@ const
import
../code_stream,
../computation,
../evm_errors,
../../common/evmforks,
./gas_costs,
./gas_meter,
@ -44,26 +45,26 @@ template handleStopDirective(k: var Vm2Ctx) =
template handleFixedGasCostsDirective(fork: EVMFork; op: Op; k: var Vm2Ctx) =
if k.cpt.tracingEnabled:
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
if k.cpt.tracingEnabled:
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
k.cpt.opcodeGastCost(op, k.cpt.gasCosts[op].cost, reason = $op)
vmOpHandlers[fork][op].run(k)
? k.cpt.opcodeGastCost(op, k.cpt.gasCosts[op].cost, reason = $op)
? vmOpHandlers[fork][op].run(k)
# If continuation is not nil, traceOpCodeEnded will be called in executeOpcodes.
if k.cpt.tracingEnabled and k.cpt.continuation.isNil:
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
# If continuation is not nil, traceOpCodeEnded will be called in executeOpcodes.
if k.cpt.tracingEnabled and k.cpt.continuation.isNil:
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
template handleOtherDirective(fork: EVMFork; op: Op; k: var Vm2Ctx) =
if k.cpt.tracingEnabled:
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
if k.cpt.tracingEnabled:
k.cpt.opIndex = k.cpt.traceOpCodeStarted(op)
vmOpHandlers[fork][op].run(k)
? vmOpHandlers[fork][op].run(k)
# If continuation is not nil, traceOpCodeEnded will be called in executeOpcodes.
if k.cpt.tracingEnabled and k.cpt.continuation.isNil:
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
# If continuation is not nil, traceOpCodeEnded will be called in executeOpcodes.
if k.cpt.tracingEnabled and k.cpt.continuation.isNil:
k.cpt.traceOpCodeEnded(op, k.cpt.opIndex)
# ------------------------------------------------------------------------------
# Private, big nasty doubly nested case matrix generator
@ -106,16 +107,6 @@ proc toCaseStmt(forkArg, opArg, k: NimNode): NimNode =
`forkCaseSubExpr`
break
else:
# FIXME-manyOpcodesNowRequireContinuations
# We used to have another clause in this case statement for various
# opcodes that *don't* need to check for a continuation. But now
# there are many opcodes that need to, because they call asyncChainTo
# (and so they set a pendingAsyncOperation and a continuation that
# needs to be noticed by the interpreter_dispatch loop). And that
# will become even more true once we implement speculative execution,
# because that will mean that even reading from the stack might
# require waiting.
#
# Anyway, the point is that now we might as well just do this check
# for *every* opcode (other than Return/Revert/etc, which need to
# break no matter what).
@ -163,7 +154,7 @@ when isMainModule and isChatty:
import ../types
proc optimised(c: Computation, fork: EVMFork) {.compileTime.} =
proc optimised(c: Computation, fork: EVMFork): EvmResultVoid {.compileTime.} =
var desc: Vm2Ctx
while true:
genOptimisedDispatcher(fork, desc.cpt.instr, desc)

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -12,10 +12,13 @@
## ===================================================
##
{.push raises: [].}
import
std/options,
../../../constants,
../../computation,
../../evm_errors,
../../stack,
../../types,
../op_codes,
@ -30,45 +33,42 @@ func slt(x, y: UInt256): bool =
let y_neg = cast[SignedWord](y.mostSignificantWord) < 0
if x_neg xor y_neg: x_neg else: x < y
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
addOp: Vm2OpFn = proc (k: var Vm2Ctx) =
addOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x01, Addition
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
lhs + rhs
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(lhs + rhs)
mulOp: Vm2OpFn = proc(k: var Vm2Ctx) =
mulOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x02, Multiplication
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
lhs * rhs
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(lhs * rhs)
subOp: Vm2OpFn = proc(k: var Vm2Ctx) =
subOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x03, Substraction
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
lhs - rhs
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(lhs - rhs)
divideOp: Vm2OpFn = proc(k: var Vm2Ctx) =
divideOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x04, Division
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
if rhs.isZero:
# EVM special casing of div by 0
zero(UInt256)
else:
lhs div rhs
let
(lhs, rhs) = ? k.cpt.stack.popInt(2)
value = if rhs.isZero:
# EVM special casing of div by 0
zero(UInt256)
else:
lhs div rhs
k.cpt.stack.push value
sdivOp: Vm2OpFn = proc(k: var Vm2Ctx) =
sdivOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x05, Signed division
let (lhs, rhs) = k.cpt.stack.popInt(2)
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
var r: UInt256
if rhs.isZero.not:
@ -82,19 +82,21 @@ const
k.cpt.stack.push(r)
moduloOp: Vm2OpFn = proc(k: var Vm2Ctx) =
moduloOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x06, Modulo
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
if rhs.isZero:
zero(UInt256)
else:
lhs mod rhs
let
(lhs, rhs) = ? k.cpt.stack.popInt(2)
value = if rhs.isZero:
zero(UInt256)
else:
lhs mod rhs
k.cpt.stack.push value
smodOp: Vm2OpFn = proc(k: var Vm2Ctx) =
smodOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x07, Signed modulo
let (lhs, rhs) = k.cpt.stack.popInt(2)
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
var r: UInt256
if rhs.isZero.not:
@ -108,54 +110,57 @@ const
k.cpt.stack.push(r)
addmodOp: Vm2OpFn = proc(k: var Vm2Ctx) =
addmodOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x08, Modulo addition
## Intermediate computations do not roll over at 2^256
let (lhs, rhs, modulus) = k.cpt.stack.popInt(3)
let
(lhs, rhs, modulus) = ? k.cpt.stack.popInt(3)
value = if modulus.isZero:
zero(UInt256)
else:
addmod(lhs, rhs, modulus)
k.cpt.stack.push:
if modulus.isZero:
zero(UInt256)
else:
addmod(lhs, rhs, modulus)
k.cpt.stack.push value
mulmodOp: Vm2OpFn = proc(k: var Vm2Ctx) =
mulmodOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x09, Modulo multiplication
## Intermediate computations do not roll over at 2^256
let (lhs, rhs, modulus) = k.cpt.stack.popInt(3)
let
(lhs, rhs, modulus) = ? k.cpt.stack.popInt(3)
value = if modulus.isZero:
zero(UInt256)
else:
mulmod(lhs, rhs, modulus)
k.cpt.stack.push:
if modulus.isZero:
zero(UInt256)
else:
mulmod(lhs, rhs, modulus)
k.cpt.stack.push value
expOp: Vm2OpFn = proc(k: var Vm2Ctx) =
expOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x0A, Exponentiation
let (base, exponent) = k.cpt.stack.popInt(2)
let (base, exponent) = ? k.cpt.stack.popInt(2)
k.cpt.opcodeGastCost(Exp,
? k.cpt.opcodeGastCost(Exp,
k.cpt.gasCosts[Exp].d_handler(exponent),
reason = "EXP: exponent bytes")
k.cpt.stack.push:
if not base.isZero:
base.pow(exponent)
elif exponent.isZero:
# https://github.com/ethereum/yellowpaper/issues/257
# https://github.com/ethereum/tests/pull/460
# https://github.com/ewasm/evm2wasm/issues/137
1.u256
else:
zero(UInt256)
let value = if not base.isZero:
base.pow(exponent)
elif exponent.isZero:
# https://github.com/ethereum/yellowpaper/issues/257
# https://github.com/ethereum/tests/pull/460
# https://github.com/ewasm/evm2wasm/issues/137
1.u256
else:
zero(UInt256)
k.cpt.stack.push value
signExtendOp: Vm2OpFn = proc(k: var Vm2Ctx) =
signExtendOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x0B, Sign extend
## Extend length of twos complement signed integer.
let (bits, value) = k.cpt.stack.popInt(2)
let (bits, value) = ? k.cpt.stack.popInt(2)
var res: UInt256
if bits <= 31.u256:
@ -170,122 +175,110 @@ const
res = value and mask
else:
res = value
k.cpt.stack.push:
res
k.cpt.stack.push res
ltOp: Vm2OpFn = proc(k: var Vm2Ctx) =
ltOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x10, Less-than comparison
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
(lhs < rhs).uint.u256
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push((lhs < rhs).uint.u256)
gtOp: Vm2OpFn = proc(k: var Vm2Ctx) =
gtOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x11, Greater-than comparison
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
(lhs > rhs).uint.u256
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push((lhs > rhs).uint.u256)
sltOp: Vm2OpFn = proc(k: var Vm2Ctx) =
sltOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x12, Signed less-than comparison
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
slt(lhs, rhs).uint.u256
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(slt(lhs, rhs).uint.u256)
sgtOp: Vm2OpFn = proc(k: var Vm2Ctx) =
sgtOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x13, Signed greater-than comparison
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
# Arguments are swapped and SLT is used.
slt(rhs, lhs).uint.u256
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
# Arguments are swapped and SLT is used.
k.cpt.stack.push(slt(rhs, lhs).uint.u256)
eqOp: Vm2OpFn = proc(k: var Vm2Ctx) =
eqOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x14, Equality comparison
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
(lhs == rhs).uint.u256
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push((lhs == rhs).uint.u256)
isZeroOp: Vm2OpFn = proc(k: var Vm2Ctx) =
isZeroOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x15, Check if zero
let (value) = k.cpt.stack.popInt(1)
k.cpt.stack.push:
value.isZero.uint.u256
let value = ? k.cpt.stack.popInt()
k.cpt.stack.push(value.isZero.uint.u256)
andOp: Vm2OpFn = proc(k: var Vm2Ctx) =
andOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x16, Bitwise AND
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
lhs and rhs
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(lhs and rhs)
orOp: Vm2OpFn = proc(k: var Vm2Ctx) =
orOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x17, Bitwise OR
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
lhs or rhs
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(lhs or rhs)
xorOp: Vm2OpFn = proc(k: var Vm2Ctx) =
xorOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x18, Bitwise XOR
let (lhs, rhs) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
lhs xor rhs
let (lhs, rhs) = ? k.cpt.stack.popInt(2)
k.cpt.stack.push(lhs xor rhs)
notOp: Vm2OpFn = proc(k: var Vm2Ctx) =
notOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x19, Check if zero
let (value) = k.cpt.stack.popInt(1)
k.cpt.stack.push:
value.not
let value = ? k.cpt.stack.popInt()
k.cpt.stack.push(value.not)
byteOp: Vm2OpFn = proc(k: var Vm2Ctx) =
byteOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0x20, Retrieve single byte from word.
let (position, value) = k.cpt.stack.popInt(2)
k.cpt.stack.push:
if position >= 32.u256:
zero(UInt256)
else:
let pos = position.truncate(int)
when system.cpuEndian == bigEndian:
cast[array[32, byte]](value)[pos].u256
else:
cast[array[32, byte]](value)[31 - pos].u256
let
(position, value) = ? k.cpt.stack.popInt(2)
val = if position >= 32.u256:
zero(UInt256)
else:
let pos = position.truncate(int)
when system.cpuEndian == bigEndian:
cast[array[32, byte]](value)[pos].u256
else:
cast[array[32, byte]](value)[31 - pos].u256
k.cpt.stack.push val
# Constantinople's new opcodes
shlOp: Vm2OpFn = proc(k: var Vm2Ctx) =
let (shift, num) = k.cpt.stack.popInt(2)
shlOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
let (shift, num) = ? k.cpt.stack.popInt(2)
let shiftLen = shift.safeInt
if shiftLen >= 256:
k.cpt.stack.push:
0
k.cpt.stack.push 0
else:
k.cpt.stack.push:
num shl shiftLen
k.cpt.stack.push(num shl shiftLen)
shrOp: Vm2OpFn = proc(k: var Vm2Ctx) =
let (shift, num) = k.cpt.stack.popInt(2)
shrOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
let (shift, num) = ? k.cpt.stack.popInt(2)
let shiftLen = shift.safeInt
if shiftLen >= 256:
k.cpt.stack.push:
0
k.cpt.stack.push 0
else:
# uint version of `shr`
k.cpt.stack.push:
num shr shiftLen
k.cpt.stack.push(num shr shiftLen)
sarOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
let
shiftLen = ? k.cpt.stack.popSafeInt()
num256 = ? k.cpt.stack.popInt()
num = cast[Int256](num256)
sarOp: Vm2OpFn = proc(k: var Vm2Ctx) =
let shiftLen = k.cpt.stack.popInt().safeInt
let num = cast[Int256](k.cpt.stack.popInt())
if shiftLen >= 256:
if num.isNegative:
k.cpt.stack.push:
cast[UInt256]((-1).i256)
k.cpt.stack.push(cast[UInt256]((-1).i256))
else:
k.cpt.stack. push:
0
k.cpt.stack. push 0
else:
# int version of `shr` then force the result
# into uint256
k.cpt.stack.push:
cast[UInt256](num shr shiftLen)
k.cpt.stack.push(cast[UInt256](num shr shiftLen))
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -18,87 +18,74 @@ import
eth/common,
../../computation,
../../stack,
../utils/utils_numeric,
../../evm_errors,
../op_codes,
./oph_defs
when not defined(evmc_enabled):
import ../../state
# Annotation helpers
{.pragma: catchRaise, gcsafe, raises: [CatchableError].}
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
blockhashOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
blockhashOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x40, Get the hash of one of the 256 most recent complete blocks.
let cpt = k.cpt
let (blockNumber) = cpt.stack.popInt(1)
block:
cpt.stack.push:
cpt.getBlockHash(blockNumber)
let
cpt = k.cpt
blockNumber = ? cpt.stack.popInt()
blockHash = cpt.getBlockHash(blockNumber)
coinBaseOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
cpt.stack.push blockHash
coinBaseOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x41, Get the block's beneficiary address.
k.cpt.stack.push:
k.cpt.getCoinbase
k.cpt.stack.push k.cpt.getCoinbase
timestampOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
timestampOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x42, Get the block's timestamp.
k.cpt.stack.push:
k.cpt.getTimestamp
k.cpt.stack.push k.cpt.getTimestamp
blocknumberOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
blocknumberOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x43, Get the block's number.
k.cpt.stack.push:
k.cpt.getBlockNumber
k.cpt.stack.push k.cpt.getBlockNumber
difficultyOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
difficultyOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x44, Get the block's difficulty
k.cpt.stack.push:
k.cpt.getDifficulty
k.cpt.stack.push k.cpt.getDifficulty
gasLimitOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
gasLimitOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x45, Get the block's gas limit
k.cpt.stack.push:
k.cpt.getGasLimit
k.cpt.stack.push k.cpt.getGasLimit
chainIdOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
chainIdOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x46, Get current chains EIP-155 unique identifier.
k.cpt.stack.push:
k.cpt.getChainId
k.cpt.stack.push k.cpt.getChainId
selfBalanceOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
selfBalanceOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x47, Get current contract's balance.
let cpt = k.cpt
block:
cpt.stack.push:
cpt.getBalance(cpt.msg.contractAddress)
cpt.stack.push cpt.getBalance(cpt.msg.contractAddress)
baseFeeOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
baseFeeOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x48, Get the block's base fee.
k.cpt.stack.push:
k.cpt.getBaseFee
k.cpt.stack.push k.cpt.getBaseFee
blobHashOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
blobHashOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x49, Get current transaction's EIP-4844 versioned hash.
let index = k.cpt.stack.popInt().safeInt
let len = k.cpt.getVersionedHashesLen
let
index = ? k.cpt.stack.popSafeInt()
len = k.cpt.getVersionedHashesLen
if index < len:
k.cpt.stack.push:
k.cpt.getVersionedHash(index)
k.cpt.stack.push k.cpt.getVersionedHash(index)
else:
k.cpt.stack.push:
0
k.cpt.stack.push 0
blobBaseFeeOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
blobBaseFeeOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x4a, Get the block's base fee.
k.cpt.stack.push:
k.cpt.getBlobBaseFee
k.cpt.stack.push k.cpt.getBlobBaseFee
# ------------------------------------------------------------------------------

View File

@ -16,7 +16,7 @@
import
../../../constants,
../../../errors,
../../evm_errors,
../../../common/evmforks,
../../computation,
../../memory,
@ -37,15 +37,12 @@ when not defined(evmc_enabled):
../../state,
../../../db/ledger
# Annotation helpers
{.pragma: catchRaise, gcsafe, raises: [CatchableError].}
# ------------------------------------------------------------------------------
# Private
# ------------------------------------------------------------------------------
type
LocalParams = tuple
LocalParams = object
gas: UInt256
value: UInt256
codeAddress: EthAddress
@ -61,8 +58,8 @@ type
gasCallEIP2929: GasInt
proc updateStackAndParams(q: var LocalParams; c: Computation) {.catchRaise.} =
c.stack.push(0)
proc updateStackAndParams(q: var LocalParams; c: Computation): EvmResultVoid =
? c.stack.push(0)
let
outLen = calcMemSize(q.memOutPos, q.memOutLen)
@ -91,81 +88,88 @@ proc updateStackAndParams(q: var LocalParams; c: Computation) {.catchRaise.} =
# The WarmStorageReadCostEIP2929 (100) is already deducted in
# the form of a constant `gasCall`
q.gasCallEIP2929 = ColdAccountAccessCost - WarmStorageReadCost
ok()
proc callParams(c: Computation): LocalParams {.catchRaise.} =
proc callParams(c: Computation): EvmResult[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
var res = LocalParams(
gas : ? c.stack.popInt(),
codeAddress : ? c.stack.popAddress(),
value : ? c.stack.popInt(),
memInPos : ? c.stack.popMemRef(),
memInLen : ? c.stack.popMemRef(),
memOutPos : ? c.stack.popMemRef(),
memOutLen : ? c.stack.popMemRef(),
sender : c.msg.contractAddress,
flags : c.msg.flags,
)
result.sender = c.msg.contractAddress
result.flags = c.msg.flags
result.contractAddress = result.codeAddress
result.updateStackAndParams(c)
res.contractAddress = res.codeAddress
? res.updateStackAndParams(c)
ok(res)
proc callCodeParams(c: Computation): LocalParams {.catchRaise.} =
proc callCodeParams(c: Computation): EvmResult[LocalParams] =
## Helper for callCodeOp()
result = c.callParams
result.contractAddress = c.msg.contractAddress
var res = ? c.callParams()
res.contractAddress = c.msg.contractAddress
ok(res)
proc delegateCallParams(c: Computation): LocalParams {.catchRaise.} =
proc delegateCallParams(c: Computation): EvmResult[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)
var res = LocalParams(
gas : ? c.stack.popInt(),
codeAddress : ? c.stack.popAddress(),
memInPos : ? c.stack.popMemRef(),
memInLen : ? c.stack.popMemRef(),
memOutPos : ? c.stack.popMemRef(),
memOutLen : ? c.stack.popMemRef(),
value : c.msg.value,
sender : c.msg.sender,
flags : c.msg.flags,
contractAddress: c.msg.contractAddress,
)
? res.updateStackAndParams(c)
ok(res)
proc staticCallParams(c: Computation): LocalParams {.catchRaise.} =
proc staticCallParams(c: Computation): EvmResult[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
var res = LocalParams(
gas : ? c.stack.popInt(),
codeAddress : ? c.stack.popAddress(),
memInPos : ? c.stack.popMemRef(),
memInLen : ? c.stack.popMemRef(),
memOutPos : ? c.stack.popMemRef(),
memOutLen : ? c.stack.popMemRef(),
value : 0.u256,
sender : c.msg.contractAddress,
flags : {EVMC_STATIC},
)
result.value = 0.u256
result.sender = c.msg.contractAddress
result.flags.incl EVMC_STATIC
result.contractAddress = result.codeAddress
result.updateStackAndParams(c)
res.contractAddress = res.codeAddress
? res.updateStackAndParams(c)
ok(res)
when evmc_enabled:
template execSubCall(c: Computation; msg: ref nimbus_message; p: LocalParams) =
c.chainTo(msg, shouldRaise = true):
c.chainTo(msg):
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.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)
? c.stack.top(1)
if not c.res.release.isNil:
c.res.release(c.res)
ok()
else:
proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) =
@ -176,334 +180,328 @@ else:
var
child = newComputation(c.vmState, false, childMsg)
c.chainTo(child, shouldRaise = true):
c.chainTo(child):
if not child.shouldBurnGas:
c.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
c.merge(child)
c.stack.top(1)
? 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))
? c.memory.write(memPos, child.output.toOpenArray(0, actualOutputSize - 1))
ok()
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
callOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
callOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 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")
if EVMC_STATIC in cpt.msg.flags:
let val = ? cpt.stack[^3, UInt256]
if val > 0.u256:
return err(opErr(StaticContext))
let
p = cpt.callParams
p = ? cpt.callParams
res = ? 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))
block:
block:
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))
var (gasCost, childGasLimit) = res
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(Call, gasCost, reason = $Call)
gasCost += p.gasCallEIP2929
if gasCost >= 0:
? cpt.opcodeGastCost(Call, gasCost, reason = $Call)
cpt.returnData.setLen(0)
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 cpt.msg.depth >= MaxCallDepth:
debug "Computation Failure",
reason = "Stack too deep",
maximumDepth = MaxCallDepth,
depth = cpt.msg.depth
cpt.gasMeter.returnGas(childGasLimit)
return ok()
if gasCost < 0 and childGasLimit <= 0:
raise newException(
OutOfGas, "Gas not enough to perform calculation (call)")
if gasCost < 0 and childGasLimit <= 0:
return err(opErr(OutOfGas))
cpt.memory.extend(p.memInPos, p.memInLen)
cpt.memory.extend(p.memOutPos, p.memOutLen)
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
let senderBalance = cpt.getBalance(p.sender)
if senderBalance < p.value:
cpt.gasMeter.returnGas(childGasLimit)
return ok()
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))
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))
ok()
# ---------------------
callCodeOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
callCodeOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xf2, Message-call into this account with an alternative account's code.
let
cpt = k.cpt
p = cpt.callCodeParams
p = ? cpt.callCodeParams
res = ? 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))
block:
block:
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))
var (gasCost, childGasLimit) = res
gasCost += p.gasCallEIP2929
if gasCost >= 0:
? cpt.opcodeGastCost(CallCode, gasCost, reason = $CallCode)
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(CallCode, gasCost, reason = $CallCode)
cpt.returnData.setLen(0)
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 ok()
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:
return err(opErr(OutOfGas))
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)
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 ok()
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))
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))
ok()
# ---------------------
delegateCallOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
delegateCallOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 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
p = ? cpt.delegateCallParams
res = ? 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))
block:
block:
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))
var (gasCost, childGasLimit) = res
gasCost += p.gasCallEIP2929
if gasCost >= 0:
? cpt.opcodeGastCost(DelegateCall, gasCost, reason = $DelegateCall)
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 ok()
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:
return err(opErr(OutOfGas))
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)
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))
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))
ok()
# ---------------------
staticCallOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
staticCallOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xfa, Static message-call into an account.
let
cpt = k.cpt
p = cpt.staticCallParams
p = ? cpt.staticCallParams
res = ? 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))
block:
block:
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))
var (gasCost, childGasLimit) = res
gasCost += p.gasCallEIP2929
if gasCost >= 0:
? cpt.opcodeGastCost(StaticCall, gasCost, reason = $StaticCall)
gasCost += p.gasCallEIP2929
if gasCost >= 0:
cpt.opcodeGastCost(StaticCall, gasCost, reason = $StaticCall)
cpt.returnData.setLen(0)
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 ok()
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:
return err(opErr(OutOfGas))
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)
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))
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))
ok()
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -12,11 +12,11 @@
## ======================================
##
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
{.push raises: [].} # basically the annotation type of a `Vm2OpFn`
import
../../../constants,
../../../errors,
../../evm_errors,
../../../common/evmforks,
../../../utils/utils,
../../computation,
@ -26,14 +26,12 @@ import
../gas_costs,
../gas_meter,
../op_codes,
../utils/utils_numeric,
./oph_defs,
./oph_helpers,
chronicles,
eth/common,
eth/common/eth_types,
stint,
strformat
stint
when not defined(evmc_enabled):
import
@ -46,15 +44,16 @@ when not defined(evmc_enabled):
when evmc_enabled:
template execSubCreate(c: Computation; msg: ref nimbus_message) =
c.chainTo(msg, shouldRaise = true):
c.chainTo(msg):
c.gasMeter.returnGas(c.res.gas_left)
if c.res.status_code == EVMC_SUCCESS:
c.stack.top(c.res.create_address)
? c.stack.top(c.res.create_address)
elif c.res.status_code == EVMC_REVERT:
# From create, only use `outputData` if child returned with `REVERT`.
c.returnData = @(makeOpenArray(c.res.output_data, c.res.output_size.int))
if not c.res.release.isNil:
c.res.release(c.res)
ok()
else:
proc execSubCreate(c: Computation; childMsg: Message;
@ -65,16 +64,17 @@ else:
var
child = newComputation(c.vmState, false, childMsg, salt)
c.chainTo(child, shouldRaise = false):
c.chainTo(child):
if not child.shouldBurnGas:
c.gasMeter.returnGas(child.gasMeter.gasRemaining)
if child.isSuccess:
c.merge(child)
c.stack.top child.msg.contractAddress
? c.stack.top child.msg.contractAddress
elif not child.error.burnsGas: # Means return was `REVERT`.
# From create, only use `outputData` if child returned with `REVERT`.
c.returnData = child.output
ok()
# ------------------------------------------------------------------------------
@ -82,23 +82,22 @@ else:
# ------------------------------------------------------------------------------
const
createOp: Vm2OpFn = proc(k: var Vm2Ctx) =
createOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xf0, Create a new account with associated code
checkInStaticContext(k.cpt)
? checkInStaticContext(k.cpt)
let
cpt = k.cpt
endowment = cpt.stack.popInt()
memPos = cpt.stack.popInt().safeInt
memLen = cpt.stack.peekInt().safeInt
endowment = ? cpt.stack.popInt()
memPos = ? cpt.stack.popSafeInt()
memLen = ? cpt.stack.peekSafeInt()
cpt.stack.top(0)
? cpt.stack.top(0)
# EIP-3860
if cpt.fork >= FkShanghai and memLen > EIP3860_MAX_INITCODE_SIZE:
trace "Initcode size exceeds maximum", initcodeSize = memLen
raise newException(InitcodeError,
&"CREATE: have {memLen}, max {EIP3860_MAX_INITCODE_SIZE}")
return err(opErr(InvalidInitCode))
let
gasParams = GasParams(
@ -106,11 +105,10 @@ const
cr_currentMemSize: cpt.memory.len,
cr_memOffset: memPos,
cr_memLength: memLen)
res = cpt.gasCosts[Create].cr_handler(1.u256, gasParams)
let gasCost = cpt.gasCosts[Create].c_handler(1.u256, gasParams).gasCost
cpt.opcodeGastCost(Create,
gasCost, reason = &"CREATE: GasCreate + {memLen} * memory expansion")
? cpt.opcodeGastCost(Create,
res.gasCost, reason = "CREATE: GasCreate + memLen * memory expansion")
cpt.memory.extend(memPos, memLen)
cpt.returnData.setLen(0)
@ -119,7 +117,7 @@ const
reason = "Stack too deep",
maxDepth = MaxCallDepth,
depth = cpt.msg.depth
return
return ok()
if endowment != 0:
let senderBalance = cpt.getBalance(cpt.msg.contractAddress)
@ -128,12 +126,12 @@ const
reason = "Insufficient funds available to transfer",
required = endowment,
balance = senderBalance
return
return ok()
var createMsgGas = cpt.gasMeter.gasRemaining
if cpt.fork >= FkTangerine:
createMsgGas -= createMsgGas div 64
cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE msg gas")
? cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE msg gas")
when evmc_enabled:
let
@ -159,27 +157,28 @@ const
sender: cpt.msg.contractAddress,
value: endowment,
data: cpt.memory.read(memPos, memLen)))
ok()
# ---------------------
create2Op: Vm2OpFn = proc(k: var Vm2Ctx) =
create2Op: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xf5, Behaves identically to CREATE, except using keccak256
checkInStaticContext(k.cpt)
? checkInStaticContext(k.cpt)
let
cpt = k.cpt
endowment = cpt.stack.popInt()
memPos = cpt.stack.popInt().safeInt
memLen = cpt.stack.popInt().safeInt
salt = ContractSalt(bytes: cpt.stack.peekInt().toBytesBE)
endowment = ? cpt.stack.popInt()
memPos = ? cpt.stack.popSafeInt()
memLen = ? cpt.stack.popSafeInt()
salt256 = ? cpt.stack.peekInt()
salt = ContractSalt(bytes: salt256.toBytesBE)
cpt.stack.top(0)
? cpt.stack.top(0)
# EIP-3860
if cpt.fork >= FkShanghai and memLen > EIP3860_MAX_INITCODE_SIZE:
trace "Initcode size exceeds maximum", initcodeSize = memLen
raise newException(InitcodeError,
&"CREATE2: have {memLen}, max {EIP3860_MAX_INITCODE_SIZE}")
return err(opErr(InvalidInitCode))
let
gasParams = GasParams(
@ -187,12 +186,12 @@ const
cr_currentMemSize: cpt.memory.len,
cr_memOffset: memPos,
cr_memLength: memLen)
res = cpt.gasCosts[Create].cr_handler(1.u256, gasParams)
var gasCost = cpt.gasCosts[Create].c_handler(1.u256, gasParams).gasCost
gasCost = gasCost + cpt.gasCosts[Create2].m_handler(0, 0, memLen)
let gasCost = res.gasCost + cpt.gasCosts[Create2].m_handler(0, 0, memLen)
cpt.opcodeGastCost(Create2,
gasCost, reason = &"CREATE2: GasCreate + {memLen} * memory expansion")
? cpt.opcodeGastCost(Create2,
gasCost, reason = "CREATE2: GasCreate + memLen * memory expansion")
cpt.memory.extend(memPos, memLen)
cpt.returnData.setLen(0)
@ -201,7 +200,7 @@ const
reason = "Stack too deep",
maxDepth = MaxCallDepth,
depth = cpt.msg.depth
return
return ok()
if endowment != 0:
let senderBalance = cpt.getBalance(cpt.msg.contractAddress)
@ -210,12 +209,12 @@ const
reason = "Insufficient funds available to transfer",
required = endowment,
balance = senderBalance
return
return ok()
var createMsgGas = cpt.gasMeter.gasRemaining
if cpt.fork >= FkTangerine:
createMsgGas -= createMsgGas div 64
cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE2 msg gas")
? cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE2 msg gas")
when evmc_enabled:
let
@ -242,6 +241,7 @@ const
sender: cpt.msg.contractAddress,
value: endowment,
data: cpt.memory.read(memPos, memLen)))
ok()
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -12,9 +12,12 @@
## ========================
##
{.push raises: [].}
import
../../types,
../../../common/evmforks,
../../evm_errors,
../op_codes
type
@ -23,7 +26,7 @@ type
Vm2OpFn* = ## general op handler, return codes are passed
## back via argument descriptor ``k``
proc(k: var Vm2Ctx) {.nimcall, gcsafe, raises: [CatchableError].}
proc(k: var Vm2Ctx): EvmResultVoid {.nimcall, gcsafe, raises:[].}
Vm2OpHanders* = tuple ## three step op code execution, typically
@ -46,7 +49,7 @@ type
const
vm2OpIgnore*: Vm2OpFn = ## No operation, placeholder function
proc(k: var Vm2Ctx) = discard
proc(k: var Vm2Ctx): EvmResultVoid = ok()
# similar to: toSeq(Fork).mapIt({it}).foldl(a+b)
Vm2OpAllForks* =

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -12,35 +12,36 @@
## ===========================================
##
{.push raises: [].}
import
std/[strformat, sequtils],
std/[sequtils],
../../stack,
../../evm_errors,
../op_codes,
./oph_defs,
./oph_gen_handlers
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
proc fnName(n: int): string {.compileTime.} =
&"dup{n}Op"
"dup" & $n & "Op"
proc opName(n: int): string {.compileTime.} =
&"Dup{n}"
"Dup" & $n
proc fnInfo(n: int): string {.compileTime.} =
var blurb = case n
of 1: "first"
of 2: "second"
of 3: "third"
else: &"{n}th"
&"Duplicate {blurb} item in the stack"
else: $n & "th"
"Duplicate " & blurb & " item in the stack"
proc dupImpl(k: var Vm2Ctx; n: int) =
proc dupImpl(k: var Vm2Ctx; n: int): EvmResultVoid =
k.cpt.stack.dup(n)
const

View File

@ -15,7 +15,7 @@
{.push raises: [].}
import
../../../errors,
../../evm_errors,
../../code_stream,
../../computation,
../../memory,
@ -26,243 +26,222 @@ import
./oph_defs,
./oph_helpers,
eth/common,
stint,
strformat
stint
when not defined(evmc_enabled):
import ../../state
# Annotation helpers
{.pragma: catchRaise, gcsafe, raises: [CatchableError].}
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
addressOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
addressOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x30, Get address of currently executing account.
k.cpt.stack.push:
k.cpt.msg.contractAddress
k.cpt.stack.push k.cpt.msg.contractAddress
# ------------------
balanceOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
balanceOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x31, Get balance of the given account.
let cpt = k.cpt
let address = cpt.stack.popAddress
block:
cpt.stack.push:
cpt.getBalance(address)
let
cpt = k.cpt
address = ? cpt.stack.popAddress
cpt.stack.push cpt.getBalance(address)
balanceEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
balanceEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x31, EIP292: Get balance of the given account for Berlin and later
let cpt = k.cpt
let address = cpt.stack.popAddress()
let
cpt = k.cpt
address = ? cpt.stack.popAddress()
gasCost = cpt.gasEip2929AccountCheck(address)
block:
let gasCost = cpt.gasEip2929AccountCheck(address)
cpt.opcodeGastCost(Balance, gasCost, reason = "Balance EIP2929")
cpt.stack.push:
cpt.getBalance(address)
? cpt.opcodeGastCost(Balance, gasCost, reason = "Balance EIP2929")
cpt.stack.push cpt.getBalance(address)
# ------------------
originOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
originOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x32, Get execution origination address.
k.cpt.stack.push:
k.cpt.getOrigin()
k.cpt.stack.push k.cpt.getOrigin()
callerOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
callerOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x33, Get caller address.
k.cpt.stack.push:
k.cpt.msg.sender
k.cpt.stack.push k.cpt.msg.sender
callValueOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
callValueOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x34, Get deposited value by the instruction/transaction
## responsible for this execution
k.cpt.stack.push:
k.cpt.msg.value
k.cpt.stack.push k.cpt.msg.value
callDataLoadOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
callDataLoadOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x35, Get input data of current environment
let (startPos) = k.cpt.stack.popInt(1)
let start = startPos.cleanMemRef
let
startPos = ? k.cpt.stack.popInt()
start = startPos.cleanMemRef
if start >= k.cpt.msg.data.len:
k.cpt.stack.push:
0
return
return k.cpt.stack.push 0
# If the data does not take 32 bytes, pad with zeros
let endRange = min(k.cpt.msg.data.len - 1, start + 31)
let presentBytes = endRange - start
let
endRange = min(k.cpt.msg.data.len - 1, start + 31)
presentBytes = endRange - start
# We rely on value being initialized with 0 by default
var value: array[32, byte]
value[0 .. presentBytes] = k.cpt.msg.data.toOpenArray(start, endRange)
k.cpt.stack.push:
value
k.cpt.stack.push value
callDataSizeOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
callDataSizeOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x36, Get size of input data in current environment.
k.cpt.stack.push:
k.cpt.msg.data.len.u256
k.cpt.stack.push k.cpt.msg.data.len.u256
callDataCopyOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
callDataCopyOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x37, Copy input data in current environment to memory.
let (memStartPos, copyStartPos, size) = k.cpt.stack.popInt(3)
let (memStartPos, copyStartPos, size) = ? k.cpt.stack.popInt(3)
# TODO tests: https://github.com/status-im/nimbus/issues/67
let (memPos, copyPos, len) =
(memStartPos.cleanMemRef, copyStartPos.cleanMemRef, size.cleanMemRef)
k.cpt.opcodeGastCost(CallDataCopy,
? k.cpt.opcodeGastCost(CallDataCopy,
k.cpt.gasCosts[CallDataCopy].m_handler(k.cpt.memory.len, memPos, len),
reason = "CallDataCopy fee")
k.cpt.memory.writePadded(k.cpt.msg.data, memPos, copyPos, len)
ok()
codeSizeOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
codeSizeOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x38, Get size of code running in current environment.
let cpt = k.cpt
block:
cpt.stack.push:
cpt.code.len
cpt.stack.push cpt.code.len
codeCopyOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
codeCopyOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x39, Copy code running in current environment to memory.
let cpt = k.cpt
block:
let (memStartPos, copyStartPos, size) = cpt.stack.popInt(3)
# TODO tests: https://github.com/status-im/nimbus/issues/67
let (memPos, copyPos, len) =
(memStartPos.cleanMemRef, copyStartPos.cleanMemRef, size.cleanMemRef)
cpt.opcodeGastCost(CodeCopy,
cpt.gasCosts[CodeCopy].m_handler(cpt.memory.len, memPos, len),
reason = "CodeCopy fee")
cpt.memory.writePadded(cpt.code.bytes, memPos, copyPos, len)
gasPriceOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3A, Get price of gas in current environment.
k.cpt.stack.push:
k.cpt.getGasPrice()
# -----------
extCodeSizeOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3b, Get size of an account's code
let cpt = k.cpt
let address = k.cpt.stack.popAddress()
block:
cpt.stack.push:
cpt.getCodeSize(address)
extCodeSizeEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3b, Get size of an account's code
let cpt = k.cpt
let address = cpt.stack.popAddress()
block:
let gasCost = cpt.gasEip2929AccountCheck(address)
cpt.opcodeGastCost(ExtCodeSize, gasCost, reason = "ExtCodeSize EIP2929")
cpt.stack.push:
cpt.getCodeSize(address)
# -----------
extCodeCopyOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3c, Copy an account's code to memory.
let cpt = k.cpt
let address = cpt.stack.popAddress()
block:
let (memStartPos, codeStartPos, size) = cpt.stack.popInt(3)
let (memPos, codePos, len) =
(memStartPos.cleanMemRef, codeStartPos.cleanMemRef, size.cleanMemRef)
cpt.opcodeGastCost(ExtCodeCopy,
cpt.gasCosts[ExtCodeCopy].m_handler(cpt.memory.len, memPos, len),
reason = "ExtCodeCopy fee")
let codeBytes = cpt.getCode(address)
cpt.memory.writePadded(codeBytes, memPos, codePos, len)
extCodeCopyEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3c, Copy an account's code to memory.
let cpt = k.cpt
let address = cpt.stack.popAddress()
block:
let (memStartPos, codeStartPos, size) = cpt.stack.popInt(3)
let (memPos, codePos, len) = (memStartPos.cleanMemRef,
codeStartPos.cleanMemRef, size.cleanMemRef)
let gasCost = cpt.gasCosts[ExtCodeCopy].m_handler(cpt.memory.len, memPos, len) +
cpt.gasEip2929AccountCheck(address)
cpt.opcodeGastCost(ExtCodeCopy, gasCost, reason = "ExtCodeCopy EIP2929")
let codeBytes = cpt.getCode(address)
cpt.memory.writePadded(codeBytes, memPos, codePos, len)
# -----------
returnDataSizeOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3d, Get size of output data from the previous call from the
## current environment.
k.cpt.stack.push:
k.cpt.returnData.len
returnDataCopyOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
## 0x3e, Copy output data from the previous call to memory.
let (memStartPos, copyStartPos, size) = k.cpt.stack.popInt(3)
let
cpt = k.cpt
(memStartPos, copyStartPos, size) = ? cpt.stack.popInt(3)
# TODO tests: https://github.com/status-im/nimbus/issues/67
let (memPos, copyPos, len) =
(memStartPos.cleanMemRef, copyStartPos.cleanMemRef, size.cleanMemRef)
let gasCost = k.cpt.gasCosts[ReturnDataCopy].m_handler(
k.cpt.memory.len, memPos, len)
k.cpt.opcodeGastCost(ReturnDataCopy, gasCost, reason = "returnDataCopy fee")
? cpt.opcodeGastCost(CodeCopy,
cpt.gasCosts[CodeCopy].m_handler(cpt.memory.len, memPos, len),
reason = "CodeCopy fee")
cpt.memory.writePadded(cpt.code.bytes, memPos, copyPos, len)
ok()
gasPriceOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3A, Get price of gas in current environment.
k.cpt.stack.push k.cpt.getGasPrice()
# -----------
extCodeSizeOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3b, Get size of an account's code
let
cpt = k.cpt
address = ? k.cpt.stack.popAddress()
cpt.stack.push cpt.getCodeSize(address)
extCodeSizeEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3b, Get size of an account's code
let
cpt = k.cpt
address = ? cpt.stack.popAddress()
gasCost = cpt.gasEip2929AccountCheck(address)
? cpt.opcodeGastCost(ExtCodeSize, gasCost, reason = "ExtCodeSize EIP2929")
cpt.stack.push cpt.getCodeSize(address)
# -----------
extCodeCopyOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3c, Copy an account's code to memory.
let
cpt = k.cpt
address = ? cpt.stack.popAddress()
(memStartPos, codeStartPos, size) = ? cpt.stack.popInt(3)
(memPos, codePos, len) =
(memStartPos.cleanMemRef, codeStartPos.cleanMemRef, size.cleanMemRef)
? cpt.opcodeGastCost(ExtCodeCopy,
cpt.gasCosts[ExtCodeCopy].m_handler(cpt.memory.len, memPos, len),
reason = "ExtCodeCopy fee")
let codeBytes = cpt.getCode(address)
cpt.memory.writePadded(codeBytes, memPos, codePos, len)
ok()
extCodeCopyEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3c, Copy an account's code to memory.
let
cpt = k.cpt
address = ? cpt.stack.popAddress()
(memStartPos, codeStartPos, size) = ? cpt.stack.popInt(3)
(memPos, codePos, len) = (memStartPos.cleanMemRef,
codeStartPos.cleanMemRef, size.cleanMemRef)
gasCost = cpt.gasCosts[ExtCodeCopy].m_handler(cpt.memory.len, memPos, len) +
cpt.gasEip2929AccountCheck(address)
? cpt.opcodeGastCost(ExtCodeCopy, gasCost, reason = "ExtCodeCopy EIP2929")
let codeBytes = cpt.getCode(address)
cpt.memory.writePadded(codeBytes, memPos, codePos, len)
ok()
# -----------
returnDataSizeOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3d, Get size of output data from the previous call from the
## current environment.
k.cpt.stack.push k.cpt.returnData.len
returnDataCopyOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3e, Copy output data from the previous call to memory.
let
(memStartPos, copyStartPos, size) = ? k.cpt.stack.popInt(3)
(memPos, copyPos, len) =
(memStartPos.cleanMemRef, copyStartPos.cleanMemRef, size.cleanMemRef)
gasCost = k.cpt.gasCosts[ReturnDataCopy].m_handler(
k.cpt.memory.len, memPos, len)
? k.cpt.opcodeGastCost(ReturnDataCopy, gasCost, reason = "returnDataCopy fee")
if copyPos + len > k.cpt.returnData.len:
raise newException(
OutOfBoundsRead,
"Return data length is not sufficient to satisfy request. Asked\n"&
&"for data from index {copyStartPos} to {copyStartPos + size}. "&
&"Return data is {k.cpt.returnData.len} in \n" &
"length")
return err(opErr(OutOfBounds))
k.cpt.memory.writePadded(k.cpt.returnData, memPos, copyPos, len)
ok()
# ---------------
extCodeHashOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
extCodeHashOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3f, Returns the keccak256 hash of a contracts code
let cpt = k.cpt
let address = k.cpt.stack.popAddress()
block:
cpt.stack.push:
cpt.getCodeHash(address)
let
cpt = k.cpt
address = ? k.cpt.stack.popAddress()
extCodeHashEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
cpt.stack.push cpt.getCodeHash(address)
extCodeHashEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x3f, EIP2929: Returns the keccak256 hash of a contracts code
let cpt = k.cpt
let address = k.cpt.stack.popAddress()
let
cpt = k.cpt
address = ? k.cpt.stack.popAddress()
gasCost = cpt.gasEip2929AccountCheck(address)
block:
let gasCost = cpt.gasEip2929AccountCheck(address)
cpt.opcodeGastCost(ExtCodeHash, gasCost, reason = "ExtCodeHash EIP2929")
cpt.stack.push:
cpt.getCodeHash(address)
? cpt.opcodeGastCost(ExtCodeHash, gasCost, reason = "ExtCodeHash EIP2929")
cpt.stack.push cpt.getCodeHash(address)
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -14,11 +14,12 @@
import
std/[strutils, macros],
./oph_defs
./oph_defs,
../../evm_errors
type
OphNumToTextFn* = proc(n: int): string
OpHanldlerImplFn* = proc(k: var Vm2Ctx; n: int)
OpHanldlerImplFn* = proc(k: var Vm2Ctx; n: int): EvmResultVoid
const
recForkSet = "Vm2OpAllForks"
@ -62,7 +63,7 @@ macro genOphHandlers*(runHandler: static[OphNumToTextFn];
# => push##Op: Vm2OpFn = proc (k: var Vm2Ctx) = ...
result.add quote do:
const `fnName`: Vm2OpFn = proc(k: var Vm2Ctx) =
const `fnName`: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
`comment`
`body`(k,`n`)
# echo ">>>", result.repr

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -12,9 +12,11 @@
## ===========================
##
{.push raises: [].}
import
../../../constants,
../../../errors,
../../evm_errors,
../../computation,
../../memory,
../../stack,
@ -24,22 +26,21 @@ import
./oph_defs,
eth/common
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
sha3Op: Vm2OpFn = proc (k: var Vm2Ctx) =
sha3Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x20, Compute Keccak-256 hash.
let (startPos, length) = k.cpt.stack.popInt(2)
let
(startPos, length) = ? k.cpt.stack.popInt(2)
(pos, len) = (startPos.safeInt, length.safeInt)
let (pos, len) = (startPos.safeInt, length.safeInt)
if pos < 0 or len < 0 or pos > 2147483648'i64:
raise newException(OutOfBoundsRead, "Out of bounds memory access")
return err(opErr(OutOfBounds))
k.cpt.opcodeGastCost(Op.Sha3,
? k.cpt.opcodeGastCost(Op.Sha3,
k.cpt.gasCosts[Op.Sha3].m_handler(k.cpt.memory.len, pos, len),
reason = "SHA3: word gas cost")
@ -49,8 +50,7 @@ const
if endRange == -1 or pos >= k.cpt.memory.len:
k.cpt.stack.push(EMPTY_SHA3)
else:
k.cpt.stack.push:
keccakHash k.cpt.memory.bytes.toOpenArray(pos, endRange)
k.cpt.stack.push keccakHash k.cpt.memory.bytes.toOpenArray(pos, endRange)
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -12,13 +12,10 @@
## ============================================
##
when defined(evmc_enabled):
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
else:
{.push raises: [].}
{.push raises: [].}
import
../../../errors,
../../evm_errors,
../../types,
../gas_costs,
eth/common,
@ -26,7 +23,9 @@ import
stint
when defined(evmc_enabled):
import ../../evmc_api, evmc/evmc
import
../../evmc_api,
evmc/evmc
else:
import
../../state,
@ -64,14 +63,14 @@ proc gasEip2929AccountCheck*(c: Computation; address: EthAddress, slot: UInt256)
else:
WarmStorageReadCost
proc checkInStaticContext*(c: Computation) {.gcsafe, raises: [CatchableError].} =
func checkInStaticContext*(c: Computation): EvmResultVoid =
## Verify static context in handler function, raise an error otherwise
if EVMC_STATIC in c.msg.flags:
# TODO: if possible, this check only appear
# when fork >= FkByzantium
raise newException(
StaticContextError,
"Cannot modify state while inside of STATICCALL context")
return err(opErr(StaticContext))
ok()
# ------------------------------------------------------------------------------
# End

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -12,9 +12,11 @@
## =======================================
##
{.push raises: [].}
import
../../../constants,
../../../errors,
../../evm_errors,
../../computation,
../../memory,
../../stack,
@ -27,38 +29,35 @@ import
./oph_helpers,
eth/common,
sequtils,
stint,
strformat
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
stint
# ------------------------------------------------------------------------------
# Private, names & settings
# ------------------------------------------------------------------------------
proc fnName(n: int): string {.compileTime.} =
&"log{n}Op"
"log" & $n & "Op"
proc opName(n: int): string {.compileTime.} =
&"Log{n}"
"Log" & $n
proc fnInfo(n: int): string {.compileTime.} =
var blurb = case n
of 1: "topic"
else: "topics"
&"Append log record with {n} {blurb}"
"Append log record with " & $n & " " & blurb
proc logImpl(c: Computation, opcode: Op, topicCount: int) =
proc logImpl(c: Computation, opcode: Op, topicCount: int): EvmResultVoid =
doAssert(topicCount in 0 .. 4)
checkInStaticContext(c)
let (memStartPosition, size) = c.stack.popInt(2)
? 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")
return err(opErr(OutOfBounds))
c.opcodeGastCost(opcode,
? c.opcodeGastCost(opcode,
c.gasCosts[opcode].m_handler(c.memory.len, memPos, len),
reason = "Memory expansion, Log topic and data gas cost")
c.memory.extend(memPos, len)
@ -66,7 +65,7 @@ proc logImpl(c: Computation, opcode: Op, topicCount: int) =
when evmc_enabled:
var topics: array[4, evmc_bytes32]
for i in 0 ..< topicCount:
topics[i].bytes = c.stack.popTopic()
topics[i].bytes = ? c.stack.popTopic()
c.host.emitLog(c.msg.contractAddress,
c.memory.read(memPos, len),
@ -75,12 +74,15 @@ proc logImpl(c: Computation, opcode: Op, topicCount: int) =
var log: Log
log.topics = newSeqOfCap[Topic](topicCount)
for i in 0 ..< topicCount:
log.topics.add(c.stack.popTopic())
let topic = ? c.stack.popTopic()
log.topics.add topic
log.data = c.memory.read(memPos, len)
log.address = c.msg.contractAddress
c.addLogEntry(log)
ok()
const
inxRange = toSeq(0 .. 4)
logOpArg = block:
@ -93,7 +95,7 @@ const
# Private, op handlers implementation
# ------------------------------------------------------------------------------
proc wrapperFn(k: var Vm2Ctx; n: int) =
proc wrapperFn(k: var Vm2Ctx; n: int): EvmResultVoid =
logImpl(k.cpt, logOpArg[n], n)
genOphHandlers fnName, fnInfo, inxRange, wrapperFn

View File

@ -15,7 +15,7 @@
{.push raises: [].}
import
../../../errors,
../../evm_errors,
../../code_stream,
../../computation,
../../memory,
@ -29,50 +29,46 @@ import
eth/common,
stint
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
when not defined(evmc_enabled):
import
../gas_meter,
../../state,
../../../db/ledger
# Annotation helpers
{.pragma: catchRaise, gcsafe, raises: [CatchableError].}
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
when evmc_enabled:
proc sstoreEvmc(c: Computation, slot, newValue: UInt256, coldAccess = 0.GasInt) {.catchRaise.} =
proc sstoreEvmc(c: Computation, slot, newValue: UInt256, coldAccess = 0.GasInt): EvmResultVoid =
let
status = c.host.setStorage(c.msg.contractAddress, slot, newValue)
gasParam = GasParams(kind: Op.Sstore, s_status: status)
gasCost = c.gasCosts[Sstore].c_handler(newValue, gasParam)[0] + coldAccess
res = ? c.gasCosts[Sstore].c_handler(newValue, gasParam)
gasCost = res.gasCost + coldAccess
c.opcodeGastCost(Sstore, gasCost, "SSTORE")
else:
proc sstoreImpl(c: Computation, slot, newValue: UInt256) {.catchRaise.} =
proc sstoreImpl(c: Computation, slot, newValue: UInt256): EvmResultVoid =
let
currentValue = c.getStorage(slot)
gasParam = GasParams(
kind: Op.Sstore,
s_currentValue: currentValue)
(gasCost, gasRefund) =
c.gasCosts[Sstore].c_handler(newValue, gasParam)
res = ? c.gasCosts[Sstore].c_handler(newValue, gasParam)
c.opcodeGastCost(Sstore, gasCost, "SSTORE")
if gasRefund > 0:
c.gasMeter.refundGas(gasRefund)
? c.opcodeGastCost(Sstore, res.gasCost, "SSTORE")
if res.gasRefund > 0:
c.gasMeter.refundGas(res.gasRefund)
c.vmState.mutateStateDB:
db.setStorage(c.msg.contractAddress, slot, newValue)
ok()
proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: UInt256, coldAccess = 0.GasInt) {.catchRaise.} =
proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: UInt256, coldAccess = 0.GasInt): EvmResultVoid =
let
stateDB = c.vmState.readOnlyStateDB
currentValue = c.getStorage(slot)
@ -82,74 +78,76 @@ else:
s_currentValue: currentValue,
s_originalValue: stateDB.getCommittedStorage(c.msg.contractAddress, slot))
res = c.gasCosts[Sstore].c_handler(newValue, gasParam)
res = ? c.gasCosts[Sstore].c_handler(newValue, gasParam)
c.opcodeGastCost(Sstore, res.gasCost + coldAccess, "SSTORE")
? c.opcodeGastCost(Sstore, res.gasCost + coldAccess, "SSTORE")
if res.gasRefund != 0:
c.gasMeter.refundGas(res.gasRefund)
c.vmState.mutateStateDB:
db.setStorage(c.msg.contractAddress, slot, newValue)
ok()
template sstoreEvmcOrSstore(cpt, slot, newValue: untyped) =
template sstoreEvmcOrSstore(cpt, slot, newValue: untyped): auto =
when evmc_enabled:
sstoreEvmc(cpt, slot, newValue, 0.GasInt)
else:
sstoreImpl(cpt, slot, newValue)
template sstoreEvmcOrNetGasMetering(cpt, slot, newValue: untyped, coldAccess = 0.GasInt) =
template sstoreEvmcOrNetGasMetering(cpt, slot, newValue: untyped, coldAccess = 0.GasInt): auto =
when evmc_enabled:
sstoreEvmc(cpt, slot, newValue, coldAccess)
else:
sstoreNetGasMeteringImpl(cpt, slot, newValue, coldAccess)
func jumpImpl(c: Computation; jumpTarget: UInt256) {.catchRaise.} =
func jumpImpl(c: Computation; jumpTarget: UInt256): EvmResultVoid =
if jumpTarget >= c.code.len.u256:
raise newException(
InvalidJumpDestination, "Invalid Jump Destination")
return err(opErr(InvalidJumpDest))
let jt = jumpTarget.truncate(int)
c.code.pc = jt
let nextOpcode = c.code.peek
if nextOpcode != JumpDest:
raise newException(InvalidJumpDestination, "Invalid Jump Destination")
return err(opErr(InvalidJumpDest))
# TODO: next check seems redundant
if not c.code.isValidOpcode(jt):
raise newException(
InvalidInstruction, "Jump resulted in invalid instruction")
return err(opErr(InvalidJumpDest))
ok()
# ------------------------------------------------------------------------------
# Private, op handlers implementation
# ------------------------------------------------------------------------------
const
popOp: Vm2OpFn = func (k: var Vm2Ctx) {.catchRaise.} =
popOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x50, Remove item from stack.
discard k.cpt.stack.popInt
k.cpt.stack.popInt.isOkOr:
return err(error)
ok()
mloadOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
mloadOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x51, Load word from memory
let (memStartPos) = k.cpt.stack.popInt(1)
let memStartPos = ? k.cpt.stack.popInt()
let memPos = memStartPos.cleanMemRef
k.cpt.opcodeGastCost(Mload,
? k.cpt.opcodeGastCost(Mload,
k.cpt.gasCosts[Mload].m_handler(k.cpt.memory.len, memPos, 32),
reason = "MLOAD: GasVeryLow + memory expansion")
k.cpt.memory.extend(memPos, 32)
k.cpt.stack.push:
k.cpt.memory.read(memPos, 32)
k.cpt.stack.push k.cpt.memory.read32Bytes(memPos)
mstoreOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
mstoreOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x52, Save word to memory
let (memStartPos, value) = k.cpt.stack.popInt(2)
let (memStartPos, value) = ? k.cpt.stack.popInt(2)
let memPos = memStartPos.cleanMemRef
k.cpt.opcodeGastCost(Mstore,
? k.cpt.opcodeGastCost(Mstore,
k.cpt.gasCosts[Mstore].m_handler(k.cpt.memory.len, memPos, 32),
reason = "MSTORE: GasVeryLow + memory expansion")
@ -157,166 +155,164 @@ const
k.cpt.memory.write(memPos, value.toBytesBE)
mstore8Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
mstore8Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x53, Save byte to memory
let (memStartPos, value) = k.cpt.stack.popInt(2)
let (memStartPos, value) = ? k.cpt.stack.popInt(2)
let memPos = memStartPos.cleanMemRef
k.cpt.opcodeGastCost(Mstore8,
? k.cpt.opcodeGastCost(Mstore8,
k.cpt.gasCosts[Mstore8].m_handler(k.cpt.memory.len, memPos, 1),
reason = "MSTORE8: GasVeryLow + memory expansion")
k.cpt.memory.extend(memPos, 1)
k.cpt.memory.write(memPos, value.toByteArrayBE[31])
# -------
sloadOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
sloadOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x54, Load word from storage.
let cpt = k.cpt # so it can safely be captured by the asyncChainTo closure below
let (slot) = cpt.stack.popInt(1)
block:
cpt.stack.push:
cpt.getStorage(slot)
let
cpt = k.cpt
slot = ? cpt.stack.popInt()
cpt.stack.push cpt.getStorage(slot)
sloadEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
sloadEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x54, EIP2929: Load word from storage for Berlin and later
let cpt = k.cpt
let (slot) = cpt.stack.popInt(1)
block:
let gasCost = cpt.gasEip2929AccountCheck(cpt.msg.contractAddress, slot)
cpt.opcodeGastCost(Sload, gasCost, reason = "sloadEIP2929")
cpt.stack.push:
cpt.getStorage(slot)
let
cpt = k.cpt
slot = ? cpt.stack.popInt()
gasCost = cpt.gasEip2929AccountCheck(cpt.msg.contractAddress, slot)
? cpt.opcodeGastCost(Sload, gasCost, reason = "sloadEIP2929")
cpt.stack.push cpt.getStorage(slot)
# -------
sstoreOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
sstoreOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x55, Save word to storage.
let cpt = k.cpt
let (slot, newValue) = cpt.stack.popInt(2)
let
cpt = k.cpt
(slot, newValue) = ? cpt.stack.popInt(2)
checkInStaticContext(cpt)
block:
sstoreEvmcOrSstore(cpt, slot, newValue)
? checkInStaticContext(cpt)
sstoreEvmcOrSstore(cpt, slot, newValue)
sstoreEIP1283Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
sstoreEIP1283Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x55, EIP1283: sstore for Constantinople and later
let cpt = k.cpt
let (slot, newValue) = cpt.stack.popInt(2)
let
cpt = k.cpt
(slot, newValue) = ? cpt.stack.popInt(2)
checkInStaticContext(cpt)
block:
sstoreEvmcOrNetGasMetering(cpt, slot, newValue)
? checkInStaticContext(cpt)
sstoreEvmcOrNetGasMetering(cpt, slot, newValue)
sstoreEIP2200Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
sstoreEIP2200Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x55, EIP2200: sstore for Istanbul and later
let cpt = k.cpt
let (slot, newValue) = cpt.stack.popInt(2)
let
cpt = k.cpt
(slot, newValue) = ? cpt.stack.popInt(2)
checkInStaticContext(cpt)
? checkInStaticContext(cpt)
const SentryGasEIP2200 = 2300
if cpt.gasMeter.gasRemaining <= SentryGasEIP2200:
raise newException(
OutOfGas,
"Gas not enough to perform EIP2200 SSTORE")
return err(opErr(OutOfGas))
block:
sstoreEvmcOrNetGasMetering(cpt, slot, newValue)
sstoreEvmcOrNetGasMetering(cpt, slot, newValue)
sstoreEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
sstoreEIP2929Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x55, EIP2929: sstore for Berlin and later
let cpt = k.cpt
let (slot, newValue) = cpt.stack.popInt(2)
checkInStaticContext(cpt)
let
cpt = k.cpt
(slot, newValue) = ? cpt.stack.popInt(2)
? checkInStaticContext(cpt)
# Minimum gas required to be present for an SSTORE call, not consumed
const SentryGasEIP2200 = 2300
if cpt.gasMeter.gasRemaining <= SentryGasEIP2200:
raise newException(OutOfGas, "Gas not enough to perform EIP2200 SSTORE")
return err(opErr(OutOfGas))
block:
var coldAccessGas = 0.GasInt
when evmc_enabled:
if cpt.host.accessStorage(cpt.msg.contractAddress, slot) == EVMC_ACCESS_COLD:
var coldAccessGas = 0.GasInt
when evmc_enabled:
if cpt.host.accessStorage(cpt.msg.contractAddress, slot) == EVMC_ACCESS_COLD:
coldAccessGas = ColdSloadCost
else:
cpt.vmState.mutateStateDB:
if not db.inAccessList(cpt.msg.contractAddress, slot):
db.accessList(cpt.msg.contractAddress, slot)
coldAccessGas = ColdSloadCost
else:
cpt.vmState.mutateStateDB:
if not db.inAccessList(cpt.msg.contractAddress, slot):
db.accessList(cpt.msg.contractAddress, slot)
coldAccessGas = ColdSloadCost
sstoreEvmcOrNetGasMetering(cpt, slot, newValue, coldAccessGas)
sstoreEvmcOrNetGasMetering(cpt, slot, newValue, coldAccessGas)
# -------
jumpOp: Vm2OpFn = func (k: var Vm2Ctx) {.catchRaise.} =
jumpOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x56, Alter the program counter
let (jumpTarget) = k.cpt.stack.popInt(1)
let jumpTarget = ? k.cpt.stack.popInt()
jumpImpl(k.cpt, jumpTarget)
jumpIOp: Vm2OpFn = func (k: var Vm2Ctx) {.catchRaise.} =
## 0x57, Conditionally alter the program counter.
let (jumpTarget, testedValue) = k.cpt.stack.popInt(2)
if testedValue.isZero.not:
jumpImpl(k.cpt, jumpTarget)
pcOp: Vm2OpFn = func (k: var Vm2Ctx) {.catchRaise.} =
jumpIOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x57, Conditionally alter the program counter.
let (jumpTarget, testedValue) = ? k.cpt.stack.popInt(2)
if testedValue.isZero:
return ok()
jumpImpl(k.cpt, jumpTarget)
pcOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x58, Get the value of the program counter prior to the increment
## corresponding to this instruction.
k.cpt.stack.push:
max(k.cpt.code.pc - 1, 0)
k.cpt.stack.push max(k.cpt.code.pc - 1, 0)
msizeOp: Vm2OpFn = func (k: var Vm2Ctx) {.catchRaise.} =
msizeOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x59, Get the size of active memory in bytes.
k.cpt.stack.push:
k.cpt.memory.len
k.cpt.stack.push k.cpt.memory.len
gasOp: Vm2OpFn = func (k: var Vm2Ctx) {.catchRaise.} =
gasOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x5a, Get the amount of available gas, including the corresponding
## reduction for the cost of this instruction.
k.cpt.stack.push:
k.cpt.gasMeter.gasRemaining
k.cpt.stack.push k.cpt.gasMeter.gasRemaining
jumpDestOp: Vm2OpFn = func (k: var Vm2Ctx) =
jumpDestOp: Vm2OpFn = func (k: var Vm2Ctx): EvmResultVoid =
## 0x5b, Mark a valid destination for jumps. This operation has no effect
## on machine state during execution.
discard
ok()
tloadOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
tloadOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x5c, Load word from transient storage.
let
slot = k.cpt.stack.popInt()
slot = ? k.cpt.stack.popInt()
val = k.cpt.getTransientStorage(slot)
k.cpt.stack.push: val
k.cpt.stack.push val
tstoreOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
tstoreOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x5d, Save word to transient storage.
checkInStaticContext(k.cpt)
? checkInStaticContext(k.cpt)
let
slot = k.cpt.stack.popInt()
val = k.cpt.stack.popInt()
slot = ? k.cpt.stack.popInt()
val = ? k.cpt.stack.popInt()
k.cpt.setTransientStorage(slot, val)
ok()
mCopyOp: Vm2OpFn = proc (k: var Vm2Ctx) {.catchRaise.} =
mCopyOp: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x5e, Copy memory
let (dst, src, size) = k.cpt.stack.popInt(3)
let (dst, src, size) = ? k.cpt.stack.popInt(3)
let (dstPos, srcPos, len) =
(dst.cleanMemRef, src.cleanMemRef, size.cleanMemRef)
k.cpt.opcodeGastCost(Mcopy,
? k.cpt.opcodeGastCost(Mcopy,
k.cpt.gasCosts[Mcopy].m_handler(k.cpt.memory.len, max(dstPos, srcPos), len),
reason = "Mcopy fee")
k.cpt.memory.copy(dstPos, srcPos, len)
ok()
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -12,36 +12,36 @@
## ====================================
##
{.push raises: [].}
import
std/[strformat, sequtils],
std/[sequtils],
../../code_stream,
../../stack,
../../evm_errors,
../op_codes,
./oph_defs,
./oph_gen_handlers
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
proc fnName(n: int): string {.compileTime.} =
&"push{n}Op"
"push" & $n & "Op"
proc opName(n: int): string {.compileTime.} =
&"Push{n}"
"Push" & $n
proc fnInfo(n: int): string {.compileTime.} =
var blurb = case n
of 1: "byte"
else: &"{n} bytes"
&"Push {blurb} on the stack"
else: $n & " bytes"
"Push " & blurb & " on the stack"
proc pushImpl(k: var Vm2Ctx; n: int) =
k.cpt.stack.push:
k.cpt.code.readVmWord(n)
proc pushImpl(k: var Vm2Ctx; n: int): EvmResultVoid =
k.cpt.stack.push k.cpt.code.readVmWord(n)
const
inxRange = toSeq(1 .. 32)
@ -66,10 +66,10 @@ genOphList fnName, fnInfo, inxRange, "vm2OpExecPush", opName
# just adding Push0 here as a special case.)
const
push0Op: Vm2OpFn = proc (k: var Vm2Ctx) =
push0Op: Vm2OpFn = proc (k: var Vm2Ctx): EvmResultVoid =
## 0x5f, push 0 onto the stack
k.cpt.stack.push(0)
vm2OpExecPushZero*: seq[Vm2OpExec] = @[
(opCode: Push0, ## 0x5f, push 0 onto the stack

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -12,36 +12,36 @@
## ====================================
##
{.push raises: [].}
import
../../evm_errors,
../../stack,
../op_codes,
./oph_defs,
./oph_gen_handlers,
sequtils,
strformat
{.push raises: [CatchableError].} # basically the annotation type of a `Vm2OpFn`
sequtils
# ------------------------------------------------------------------------------
# Private, names & settings
# ------------------------------------------------------------------------------
proc fnName(n: int): string {.compileTime.} =
&"swap{n}Op"
"swap" & $n & "Op"
proc opName(n: int): string {.compileTime.} =
&"Swap{n}"
"Swap" & $n
proc fnInfo(n: int): string {.compileTime.} =
var blurb = case n+1
of 1: "first"
of 2: "second"
of 3: "third"
else: &"{n+1}th"
&"Exchange first and {blurb} stack items"
else: $(n+1) & "th"
"Exchange first and " & blurb & " stack items"
proc swapImpl(k: var Vm2Ctx; n: int) =
func swapImpl(k: var Vm2Ctx; n: int): EvmResultVoid =
k.cpt.stack.swap(n)
const

View File

@ -15,7 +15,7 @@
{.push raises: [].}
import
../../../errors,
../../evm_errors,
../../computation,
../../memory,
../../stack,
@ -33,33 +33,31 @@ when not defined(evmc_enabled):
../../state,
../../../db/ledger
# Annotation helpers
{.pragma: catchRaise, gcsafe, raises: [CatchableError].}
# ------------------------------------------------------------------------------
# Private
# ------------------------------------------------------------------------------
const
returnOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
returnOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xf3, Halt execution returning output data.
let (startPos, size) = k.cpt.stack.popInt(2)
let (startPos, size) = ? k.cpt.stack.popInt(2)
let (pos, len) = (startPos.cleanMemRef, size.cleanMemRef)
k.cpt.opcodeGastCost(Return,
? k.cpt.opcodeGastCost(Return,
k.cpt.gasCosts[Return].m_handler(k.cpt.memory.len, pos, len),
reason = "RETURN")
k.cpt.memory.extend(pos, len)
k.cpt.output = k.cpt.memory.read(pos, len)
ok()
revertOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
revertOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xfd, Halt execution reverting state changes but returning data
## and remaining gas.
let (startPos, size) = k.cpt.stack.popInt(2)
let (startPos, size) = ? k.cpt.stack.popInt(2)
let (pos, len) = (startPos.cleanMemRef, size.cleanMemRef)
k.cpt.opcodeGastCost(Revert,
? k.cpt.opcodeGastCost(Revert,
k.cpt.gasCosts[Revert].m_handler(k.cpt.memory.len, pos, len),
reason = "REVERT")
@ -67,50 +65,46 @@ const
k.cpt.output = k.cpt.memory.read(pos, len)
# setError(msg, false) will signal cheap revert
k.cpt.setError(EVMC_REVERT, "REVERT opcode executed", false)
ok()
invalidOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
raise newException(InvalidInstruction,
"Invalid instruction, received an opcode " &
"not implemented in the current fork. " &
$k.cpt.fork & " " & $k.cpt.instr)
invalidOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
err(opErr(InvalidInstruction))
# -----------
selfDestructOp: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
selfDestructOp: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## 0xff, Halt execution and register account for later deletion.
let cpt = k.cpt
let beneficiary = cpt.stack.popAddress()
let beneficiary = ? cpt.stack.popAddress()
when defined(evmc_enabled):
block:
cpt.selfDestruct(beneficiary)
else:
block:
cpt.selfDestruct(beneficiary)
ok()
selfDestructEIP150Op: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
selfDestructEIP150Op: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## selfDestructEip150 (auto generated comment)
let cpt = k.cpt
let beneficiary = cpt.stack.popAddress()
let beneficiary = ? cpt.stack.popAddress()
block:
let gasParams = GasParams(
kind: SelfDestruct,
sd_condition: not cpt.accountExists(beneficiary))
let gasCost =
cpt.gasCosts[SelfDestruct].c_handler(0.u256, gasParams).gasCost
cpt.opcodeGastCost(SelfDestruct,
gasCost, reason = "SELFDESTRUCT EIP150")
let res = ? cpt.gasCosts[SelfDestruct].c_handler(0.u256, gasParams)
? cpt.opcodeGastCost(SelfDestruct,
res.gasCost, reason = "SELFDESTRUCT EIP150")
cpt.selfDestruct(beneficiary)
ok()
selfDestructEIP161Op: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
selfDestructEIP161Op: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## selfDestructEip161 (auto generated comment)
let cpt = k.cpt
checkInStaticContext(cpt)
? checkInStaticContext(cpt)
let beneficiary = cpt.stack.popAddress()
let beneficiary = ? cpt.stack.popAddress()
block:
let
isDead = not cpt.accountExists(beneficiary)
@ -120,30 +114,30 @@ const
kind: SelfDestruct,
sd_condition: isDead and not balance.isZero)
let gasCost =
cpt.gasCosts[SelfDestruct].c_handler(0.u256, gasParams).gasCost
cpt.opcodeGastCost(SelfDestruct,
gasCost, reason = "SELFDESTRUCT EIP161")
let res = ? cpt.gasCosts[SelfDestruct].c_handler(0.u256, gasParams)
? cpt.opcodeGastCost(SelfDestruct,
res.gasCost, reason = "SELFDESTRUCT EIP161")
cpt.selfDestruct(beneficiary)
ok()
selfDestructEIP2929Op: Vm2OpFn = proc(k: var Vm2Ctx) {.catchRaise.} =
selfDestructEIP2929Op: Vm2OpFn = proc(k: var Vm2Ctx): EvmResultVoid =
## selfDestructEIP2929 (auto generated comment)
let cpt = k.cpt
checkInStaticContext(cpt)
? checkInStaticContext(cpt)
let beneficiary = cpt.stack.popAddress()
let beneficiary = ? cpt.stack.popAddress()
block:
let
isDead = not cpt.accountExists(beneficiary)
balance = cpt.getBalance(cpt.msg.contractAddress)
let gasParams = GasParams(
kind: SelfDestruct,
sd_condition: isDead and not balance.isZero)
let
gasParams = GasParams(
kind: SelfDestruct,
sd_condition: isDead and not balance.isZero)
res = ? cpt.gasCosts[SelfDestruct].c_handler(0.u256, gasParams)
var gasCost =
cpt.gasCosts[SelfDestruct].c_handler(0.u256, gasParams).gasCost
var gasCost = res.gasCost
when evmc_enabled:
if cpt.host.accessAccount(beneficiary) == EVMC_ACCESS_COLD:
@ -154,9 +148,10 @@ const
db.accessList(beneficiary)
gasCost = gasCost + ColdAccountAccessCost
cpt.opcodeGastCost(SelfDestruct,
? cpt.opcodeGastCost(SelfDestruct,
gasCost, reason = "SELFDESTRUCT EIP2929")
cpt.selfDestruct(beneficiary)
ok()
# ------------------------------------------------------------------------------
# Public, op exec table entries

View File

@ -17,7 +17,7 @@ import
std/[macros, strformat],
pkg/[chronicles, chronos, stew/byteutils],
".."/[constants, db/ledger],
"."/[code_stream, computation],
"."/[code_stream, computation, evm_errors],
"."/[message, precompiles, state, types],
./interpreter/[op_dispatcher, gas_costs]
@ -38,8 +38,7 @@ when optimizationCondition:
# this is a top level pragma since nim 1.6.16
{.optimization: speed.}
proc selectVM(c: Computation, fork: EVMFork, shouldPrepareTracer: bool)
{.gcsafe, raises: [CatchableError].} =
proc selectVM(c: Computation, fork: EVMFork, shouldPrepareTracer: bool): EvmResultVoid =
## Op code execution handler main loop.
var desc: Vm2Ctx
desc.cpt = c
@ -107,6 +106,8 @@ proc selectVM(c: Computation, fork: EVMFork, shouldPrepareTracer: bool)
genLowMemDispatcher(fork, c.instr, desc)
ok()
proc beforeExecCall(c: Computation) =
c.snapshot()
if c.msg.kind == EVMC_CALL:
@ -129,12 +130,12 @@ proc afterExecCall(c: Computation) =
else:
c.rollback()
proc beforeExecCreate(c: Computation): bool
{.gcsafe, raises: [ValueError].} =
proc beforeExecCreate(c: Computation): bool =
c.vmState.mutateStateDB:
let nonce = db.getNonce(c.msg.sender)
if nonce+1 < nonce:
c.setError(&"Nonce overflow when sender={c.msg.sender.toHex} wants to create contract", false)
let sender = c.msg.sender.toHex
c.setError("Nonce overflow when sender=" & sender & " wants to create contract", false)
return true
db.setNonce(c.msg.sender, nonce+1)
@ -162,8 +163,7 @@ proc beforeExecCreate(c: Computation): bool
return false
proc afterExecCreate(c: Computation)
{.gcsafe, raises: [CatchableError].} =
proc afterExecCreate(c: Computation) =
if c.isSuccess:
# This can change `c.isSuccess`.
c.writeContract()
@ -194,9 +194,7 @@ func msgToOp(msg: Message): Op =
return StaticCall
MsgKindToOp[msg.kind]
proc beforeExec(c: Computation): bool
{.gcsafe, raises: [ValueError].} =
proc beforeExec(c: Computation): bool =
if c.msg.depth > 0:
c.vmState.captureEnter(c,
msgToOp(c.msg),
@ -210,9 +208,7 @@ proc beforeExec(c: Computation): bool
else:
c.beforeExecCreate()
proc afterExec(c: Computation)
{.gcsafe, raises: [CatchableError].} =
proc afterExec(c: Computation) =
if not c.msg.isCreate:
c.afterExecCall()
else:
@ -226,76 +222,74 @@ proc afterExec(c: Computation)
# Public functions
# ------------------------------------------------------------------------------
proc executeOpcodes*(c: Computation, shouldPrepareTracer: bool = true)
{.gcsafe, raises: [].} =
template handleEvmError(x: EvmErrorObj) =
let
msg = $x.code
depth = $(c.msg.depth + 1) # plus one to match tracer depth, and avoid confusion
c.setError("Opcode Dispatch Error: " & msg & ", depth=" & depth, true)
proc executeOpcodes*(c: Computation, shouldPrepareTracer: bool = true) =
let fork = c.fork
block:
block blockOne:
if c.continuation.isNil and c.execPrecompiles(fork):
break
break blockOne
try:
let cont = c.continuation
if not cont.isNil:
c.continuation = nil
cont()
let nextCont = c.continuation
if nextCont.isNil:
# FIXME-Adam: I hate how convoluted this is. See also the comment in
# op_dispatcher.nim. The idea here is that we need to call
# traceOpCodeEnded at the end of the opcode (and only if there
# hasn't been an exception thrown); otherwise we run into problems
# if an exception (e.g. out of gas) is thrown during a continuation.
# So this code says, "If we've just run a continuation, but there's
# no *subsequent* continuation, then the opcode is done."
if c.tracingEnabled and not(cont.isNil) and nextCont.isNil:
c.traceOpCodeEnded(c.instr, c.opIndex)
case c.instr
of Return, Revert, SelfDestruct: # FIXME-Adam: HACK, fix this in a clean way; I think the idea is that these are the ones from the "always break" case in op_dispatcher
discard
else:
c.selectVM(fork, shouldPrepareTracer)
else:
# Return up to the caller, which will run the async operation or child
# and then call this proc again.
discard
except CatchableError as e:
let
msg = e.msg
depth = $(c.msg.depth + 1) # plus one to match tracer depth, and avoid confusion
c.setError("Opcode Dispatch Error: " & msg & ", depth=" & depth, true)
let cont = c.continuation
if not cont.isNil:
c.continuation = nil
cont().isOkOr:
handleEvmError(error)
break blockOne
let nextCont = c.continuation
if not nextCont.isNil:
# Return up to the caller, which will run the child
# and then call this proc again.
break blockOne
# FIXME-Adam: I hate how convoluted this is. See also the comment in
# op_dispatcher.nim. The idea here is that we need to call
# traceOpCodeEnded at the end of the opcode (and only if there
# hasn't been an exception thrown); otherwise we run into problems
# if an exception (e.g. out of gas) is thrown during a continuation.
# So this code says, "If we've just run a continuation, but there's
# no *subsequent* continuation, then the opcode is done."
if c.tracingEnabled and not(cont.isNil) and nextCont.isNil:
c.traceOpCodeEnded(c.instr, c.opIndex)
if c.instr == Return or
c.instr == Revert or
c.instr == SelfDestruct:
break blockOne
c.selectVM(fork, shouldPrepareTracer).isOkOr:
handleEvmError(error)
break blockOne # this break is not needed but make the flow clear
if c.isError() and c.continuation.isNil:
if c.tracingEnabled: c.traceError()
when vm_use_recursion:
# Recursion with tiny stack frame per level.
proc execCallOrCreate*(c: Computation)
{.gcsafe, raises: [CatchableError].} =
proc execCallOrCreate*(c: Computation) =
defer: c.dispose()
if c.beforeExec():
return
c.executeOpcodes()
while not c.continuation.isNil:
# If there's a continuation, then it's because there's either
# a child (i.e. call or create) or a pendingAsyncOperation.
if not c.pendingAsyncOperation.isNil:
let p = c.pendingAsyncOperation
c.pendingAsyncOperation = nil
doAssert(p.finished(), "In synchronous mode, every async operation should be an already-resolved Future.")
c.executeOpcodes(false)
# a child (i.e. call or create)
when evmc_enabled:
c.res = c.host.call(c.child[])
else:
when evmc_enabled:
c.res = c.host.call(c.child[])
else:
execCallOrCreate(c.child)
c.child = nil
c.executeOpcodes()
execCallOrCreate(c.child)
c.child = nil
c.executeOpcodes()
c.afterExec()
else:
proc execCallOrCreate*(cParam: Computation)
{.gcsafe, raises: [CatchableError].} =
proc execCallOrCreate*(cParam: Computation) =
var (c, before, shouldPrepareTracer) = (cParam, true, true)
defer:
while not c.isNil:
@ -311,19 +305,13 @@ else:
if c.continuation.isNil:
c.afterExec()
break
if not c.pendingAsyncOperation.isNil:
before = false
shouldPrepareTracer = false
let p = c.pendingAsyncOperation
c.pendingAsyncOperation = nil
doAssert(p.finished(), "In synchronous mode, every async operation should be an already-resolved Future.")
else:
(before, shouldPrepareTracer, c.child, c, c.parent) = (true, true, nil.Computation, c.child, c)
(before, shouldPrepareTracer, c.child, c, c.parent) = (true, true, nil.Computation, c.child, c)
if c.parent.isNil:
break
c.dispose()
(before, shouldPrepareTracer, c.parent, c) = (false, true, nil.Computation, c.parent)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -1,30 +1,31 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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.
# * 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.
{.push raises: [].}
import
chronicles,
./validation,
./evm_errors,
./interpreter/utils/utils_numeric
type
Memory* = ref object
EvmMemoryRef* = ref object
bytes*: seq[byte]
logScope:
topics = "vm memory"
proc newMemory*: Memory =
func new*(_: type EvmMemoryRef): EvmMemoryRef =
new(result)
result.bytes = @[]
proc len*(memory: Memory): int =
func len*(memory: EvmMemoryRef): int =
result = memory.bytes.len
proc extend*(memory: var Memory; startPos: Natural; size: Natural) =
func extend*(memory: EvmMemoryRef; startPos: Natural; size: Natural) =
if size == 0:
return
let newSize = ceil32(startPos + size)
@ -32,33 +33,40 @@ proc extend*(memory: var Memory; startPos: Natural; size: Natural) =
return
memory.bytes.setLen(newSize)
proc newMemory*(size: Natural): Memory =
result = newMemory()
func new*(_: type EvmMemoryRef, size: Natural): EvmMemoryRef =
result = EvmMemoryRef.new()
result.extend(0, size)
proc read*(memory: var Memory, startPos: Natural, size: Natural): seq[byte] =
func read*(memory: EvmMemoryRef, startPos: Natural, size: Natural): seq[byte] =
result = newSeq[byte](size)
if size > 0:
copyMem(result[0].addr, memory.bytes[startPos].addr, size)
template read32Bytes*(memory: EvmMemoryRef, startPos): auto =
memory.bytes.toOpenArray(startPos, startPos + 31)
when defined(evmc_enabled):
proc readPtr*(memory: var Memory, startPos: Natural): ptr byte =
func readPtr*(memory: EvmMemoryRef, startPos: Natural): ptr byte =
if memory.bytes.len == 0 or startPos >= memory.bytes.len: return
result = memory.bytes[startPos].addr
proc write*(memory: var Memory, startPos: Natural, value: openArray[byte]) =
func write*(memory: EvmMemoryRef, startPos: Natural, value: openArray[byte]): EvmResultVoid =
let size = value.len
if size == 0:
return
validateLte(startPos + size, memory.len)
if startPos + size > memory.len:
return err(memErr(MemoryFull))
for z, b in value:
memory.bytes[z + startPos] = b
ok()
proc write*(memory: var Memory, startPos: Natural, value: byte) =
validateLte(startPos + 1, memory.len)
func write*(memory: EvmMemoryRef, startPos: Natural, value: byte): EvmResultVoid =
if startPos + 1 > memory.len:
return err(memErr(MemoryFull))
memory.bytes[startPos] = value
ok()
proc copy*(memory: var Memory, dst, src, len: Natural) =
func copy*(memory: EvmMemoryRef, dst, src, len: Natural) =
if len <= 0: return
memory.extend(max(dst, src), len)
if dst == src:
@ -70,7 +78,7 @@ proc copy*(memory: var Memory, dst, src, len: Natural) =
for i in countdown(len-1, 0):
memory.bytes[dst+i] = memory.bytes[src+i]
proc writePadded*(memory: var Memory, data: openArray[byte],
func writePadded*(memory: EvmMemoryRef, data: openArray[byte],
memPos, dataPos, len: Natural) =
memory.extend(memPos, len)

View File

@ -13,14 +13,16 @@ import
results,
"."/[types, blake2b_f, blscurve],
./interpreter/[gas_meter, gas_costs, utils/utils_numeric],
../errors, eth/[common, keys], chronicles,
nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups],
eth/[common, keys],
chronicles,
nimcrypto/[ripemd, sha2, utils],
bncurve/[fields, groups],
../common/evmforks,
../core/eip4844,
./modexp,
./evm_errors,
./computation
type
PrecompileAddresses* = enum
# Frontier to Spurious Dragron
@ -50,8 +52,15 @@ type
# paBlsMapG2
# Cancun
SigRes = object
msgHash: array[32, byte]
sig: Signature
proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
func getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
if fork < FkByzantium: paIdentity
elif fork < FkIstanbul: paPairing
# EIP 2537: disabled
@ -60,60 +69,48 @@ proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
elif fork < FkCancun: paBlake2bf
else: PrecompileAddresses.high
proc validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool =
func validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool =
(addrByte in PrecompileAddresses.low.byte .. maxPrecompileAddr)
proc validPrecompileAddr(addrByte: byte, fork: EVMFork): bool =
func validPrecompileAddr(addrByte: byte, fork: EVMFork): bool =
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
validPrecompileAddr(addrByte, maxPrecompileAddr.byte)
iterator activePrecompiles*(fork: EVMFork): EthAddress =
var res: EthAddress
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
for c in PrecompileAddresses.low..maxPrecompileAddr:
if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
res[^1] = c.byte
yield res
func activePrecompilesList*(fork: EVMFork): seq[EthAddress] =
for address in activePrecompiles(fork):
result.add address
proc getSignature(c: Computation): (array[32, byte], Signature) =
func getSignature(c: Computation): EvmResult[SigRes] =
# input is Hash, V, R, S
template data: untyped = c.msg.data
var bytes: array[65, byte] # will hold R[32], S[32], V[1], in that order
let maxPos = min(data.high, 127)
# if we don't have at minimum 64 bytes, there can be no valid V
if maxPos >= 63:
let v = data[63]
# check if V[32] is 27 or 28
if not (v.int in 27..28):
raise newException(ValidationError, "Invalid V in getSignature")
for x in 32..<63:
if data[x] != 0:
raise newException(ValidationError, "Invalid V in getSignature")
if maxPos < 63:
return err(prcErr(PrcInvalidSig))
bytes[64] = v - 27
let v = data[63]
# check if V[32] is 27 or 28
if not (v.int in 27..28):
return err(prcErr(PrcInvalidSig))
for x in 32..<63:
if data[x] != 0:
return err(prcErr(PrcInvalidSig))
# if there is more data for R and S, copy it. Else, defaulted zeroes are
# used for R and S
if maxPos >= 64:
# Copy message data to buffer
bytes[0..(maxPos-64)] = data[64..maxPos]
bytes[64] = v - 27
let sig = Signature.fromRaw(bytes)
if sig.isErr:
raise newException(ValidationError, "Could not recover signature c")
result[1] = sig[]
# if there is more data for R and S, copy it. Else, defaulted zeroes are
# used for R and S
if maxPos >= 64:
# Copy message data to buffer
bytes[0..(maxPos-64)] = data[64..maxPos]
# extract message hash, only need to copy when there is a valid signature
result[0][0..31] = data[0..31]
else:
raise newException(ValidationError, "Invalid V in getSignature")
let sig = Signature.fromRaw(bytes).valueOr:
return err(prcErr(PrcInvalidSig))
var res = SigRes(sig: sig)
proc simpleDecode*(dst: var FQ2, src: openArray[byte]): bool {.noinit.} =
# extract message hash, only need to copy when there is a valid signature
res.msgHash[0..31] = data[0..31]
ok(res)
func simpleDecode(dst: var FQ2, src: openArray[byte]): bool {.noinit.} =
# bypassing FQ2.fromBytes
# because we want to check `value > modulus`
result = false
@ -121,78 +118,85 @@ proc simpleDecode*(dst: var FQ2, src: openArray[byte]): bool {.noinit.} =
dst.c0.fromBytes(src.toOpenArray(32, 63)):
result = true
template simpleDecode*(dst: var FQ, src: openArray[byte]): bool =
template simpleDecode(dst: var FQ, src: openArray[byte]): bool =
fromBytes(dst, src)
proc getPoint[T: G1|G2](t: typedesc[T], data: openArray[byte]): Point[T] =
func getPoint[T: G1|G2](_: typedesc[T], data: openArray[byte]): EvmResult[Point[T]] =
when T is G1:
const nextOffset = 32
var px, py: FQ
else:
const nextOffset = 64
var px, py: FQ2
if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)):
raise newException(ValidationError, "Could not get point value")
return err(prcErr(PrcInvalidPoint))
if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)):
raise newException(ValidationError, "Could not get point value")
return err(prcErr(PrcInvalidPoint))
if px.isZero() and py.isZero():
result = T.zero()
ok(T.zero())
else:
var ap: AffinePoint[T]
if not ap.init(px, py):
raise newException(ValidationError, "Point is not on curve")
result = ap.toJacobian()
return err(prcErr(PrcInvalidPoint))
ok(ap.toJacobian())
proc getFR(data: openArray[byte]): FR =
if not result.fromBytes2(data):
raise newException(ValidationError, "Could not get FR value")
func getFR(data: openArray[byte]): EvmResult[FR] =
var res: FR
if not res.fromBytes2(data):
return err(prcErr(PrcInvalidPoint))
ok(res)
proc ecRecover*(c: Computation) =
c.gasMeter.consumeGas(
# ------------------------------------------------------------------------------
# Precompiles functions
# ------------------------------------------------------------------------------
func ecRecover(c: Computation): EvmResultVoid =
? c.gasMeter.consumeGas(
GasECRecover,
reason="ECRecover Precompile")
var
(msgHash, sig) = c.getSignature()
var pubkey = recover(sig, SkMessage(msgHash))
if pubkey.isErr:
raise newException(ValidationError, "Could not derive public key from c")
let
sig = ? c.getSignature()
pubkey = recover(sig.sig, SkMessage(sig.msgHash)).valueOr:
return err(prcErr(PrcInvalidSig))
c.output.setLen(32)
c.output[12..31] = pubkey[].toCanonicalAddress()
#trace "ECRecover precompile", derivedKey = pubkey[].toCanonicalAddress()
c.output[12..31] = pubkey.toCanonicalAddress()
ok()
proc sha256*(c: Computation) =
func sha256(c: Computation): EvmResultVoid =
let
wordCount = wordCount(c.msg.data.len)
gasFee = GasSHA256 + wordCount * GasSHA256Word
c.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile")
? c.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile")
c.output = @(sha2.sha256.digest(c.msg.data).data)
#trace "SHA256 precompile", output = c.output.toHex
ok()
proc ripemd160*(c: Computation) =
func ripemd160(c: Computation): EvmResultVoid =
let
wordCount = wordCount(c.msg.data.len)
gasFee = GasRIPEMD160 + wordCount * GasRIPEMD160Word
c.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile")
? c.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile")
c.output.setLen(32)
c.output[12..31] = @(ripemd.ripemd160.digest(c.msg.data).data)
#trace "RIPEMD160 precompile", output = c.output.toHex
ok()
proc identity*(c: Computation) =
func identity(c: Computation): EvmResultVoid =
let
wordCount = wordCount(c.msg.data.len)
gasFee = GasIdentity + wordCount * GasIdentityWord
c.gasMeter.consumeGas(gasFee, reason="Identity Precompile")
? c.gasMeter.consumeGas(gasFee, reason="Identity Precompile")
c.output = c.msg.data
#trace "Identity precompile", output = c.output.toHex
ok()
proc modExpFee(c: Computation, baseLen, expLen, modLen: UInt256, fork: EVMFork): GasInt =
func modExpFee(c: Computation,
baseLen, expLen, modLen: UInt256,
fork: EVMFork): EvmResult[GasInt] =
template data: untyped {.dirty.} =
c.msg.data
@ -237,15 +241,15 @@ proc modExpFee(c: Computation, baseLen, expLen, modLen: UInt256, fork: EVMFork):
else: gasCalc(mulComplexity, GasQuadDivisor)
if gasFee > high(GasInt).u256:
raise newException(OutOfGas, "modExp gas overflow")
result = gasFee.truncate(GasInt)
return err(gasErr(OutOfGas))
var res = gasFee.truncate(GasInt)
# EIP2565: modExp gas cost
if fork >= FkBerlin and result < 200.GasInt:
result = 200.GasInt
if fork >= FkBerlin and res < 200.GasInt:
res = 200.GasInt
ok(res)
proc modExp*(c: Computation, fork: EVMFork = FkByzantium) =
func modExp(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
## Modular exponentiation precompiled contract
## Yellow Paper Appendix E
## EIP-198 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md
@ -261,17 +265,17 @@ proc modExp*(c: Computation, fork: EVMFork = FkByzantium) =
expLen = expL.safeInt
modLen = modL.safeInt
let gasFee = modExpFee(c, baseL, expL, modL, fork)
c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")
let gasFee = ? modExpFee(c, baseL, expL, modL, fork)
? c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")
if baseLen == 0 and modLen == 0:
# This is a special case where expLength can be very big.
c.output = @[]
return
return ok()
const maxSize = int32.high.u256
if baseL > maxSize or expL > maxSize or modL > maxSize:
raise newException(EVMError, "The Nimbus VM doesn't support oversized modExp operand")
return err(prcErr(PrcInvalidParam))
# TODO:
# add EVM special case:
@ -291,10 +295,11 @@ proc modExp*(c: Computation, fork: EVMFork = FkByzantium) =
else:
c.output = newSeq[byte](modLen)
c.output[^output.len..^1] = output[0..^1]
ok()
proc bn256ecAdd*(c: Computation, fork: EVMFork = FkByzantium) =
func bn256ecAdd(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
let gasFee = if fork < FkIstanbul: GasECAdd else: GasECAddIstanbul
c.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile")
? c.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile")
var
input: array[128, byte]
@ -302,18 +307,19 @@ proc bn256ecAdd*(c: Computation, fork: EVMFork = FkByzantium) =
# Padding data
let len = min(c.msg.data.len, 128) - 1
input[0..len] = c.msg.data[0..len]
var p1 = G1.getPoint(input.toOpenArray(0, 63))
var p2 = G1.getPoint(input.toOpenArray(64, 127))
var p1 = ? G1.getPoint(input.toOpenArray(0, 63))
var p2 = ? G1.getPoint(input.toOpenArray(64, 127))
var apo = (p1 + p2).toAffine()
if isSome(apo):
# we can discard here because we supply proper buffer
discard apo.get().toBytes(output)
c.output = @output
ok()
proc bn256ecMul*(c: Computation, fork: EVMFork = FkByzantium) =
func bn256ecMul(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
let gasFee = if fork < FkIstanbul: GasECMul else: GasECMulIstanbul
c.gasMeter.consumeGas(gasFee, reason="ecMul Precompile")
? c.gasMeter.consumeGas(gasFee, reason="ecMul Precompile")
var
input: array[96, byte]
@ -322,26 +328,27 @@ proc bn256ecMul*(c: Computation, fork: EVMFork = FkByzantium) =
# Padding data
let len = min(c.msg.data.len, 96) - 1
input[0..len] = c.msg.data[0..len]
var p1 = G1.getPoint(input.toOpenArray(0, 63))
var fr = getFR(input.toOpenArray(64, 95))
var p1 = ? G1.getPoint(input.toOpenArray(0, 63))
var fr = ? getFR(input.toOpenArray(64, 95))
var apo = (p1 * fr).toAffine()
if isSome(apo):
# we can discard here because we supply buffer of proper size
discard apo.get().toBytes(output)
c.output = @output
ok()
proc bn256ecPairing*(c: Computation, fork: EVMFork = FkByzantium) =
func bn256ecPairing(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
let msglen = len(c.msg.data)
if msglen mod 192 != 0:
raise newException(ValidationError, "Invalid input length")
return err(prcErr(PrcInvalidParam))
let numPoints = msglen div 192
let gasFee = if fork < FkIstanbul:
GasECPairingBase + numPoints * GasECPairingPerPoint
else:
GasECPairingBaseIstanbul + numPoints * GasECPairingPerPointIstanbul
c.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile")
? c.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile")
var output: array[32, byte]
if msglen == 0:
@ -356,9 +363,9 @@ proc bn256ecPairing*(c: Computation, fork: EVMFork = FkByzantium) =
for i in 0..<count:
let s = i * 192
# Loading AffinePoint[G1], bytes from [0..63]
var p1 = G1.getPoint(c.msg.data.toOpenArray(s, s + 63))
var p1 = ? G1.getPoint(c.msg.data.toOpenArray(s, s + 63))
# Loading AffinePoint[G2], bytes from [64..191]
var p2 = G2.getPoint(c.msg.data.toOpenArray(s + 64, s + 191))
var p2 = ? G2.getPoint(c.msg.data.toOpenArray(s + 64, s + 191))
# Accumulate pairing result
acc = acc * pairing(p1, p2)
@ -367,22 +374,25 @@ proc bn256ecPairing*(c: Computation, fork: EVMFork = FkByzantium) =
discard BNU256.one().toBytes(output)
c.output = @output
ok()
proc blake2bf*(c: Computation) =
func blake2bf(c: Computation): EvmResultVoid =
template input: untyped =
c.msg.data
if len(input) == blake2FInputLength:
let gasFee = GasInt(beLoad32(input, 0))
c.gasMeter.consumeGas(gasFee, reason="blake2bf Precompile")
? c.gasMeter.consumeGas(gasFee, reason="blake2bf Precompile")
var output: array[64, byte]
if not blake2b_F(input, output):
raise newException(ValidationError, "Blake2b F function invalid input")
return err(prcErr(PrcInvalidParam))
else:
c.output = @output
ok()
proc blsG1Add*(c: Computation) =
#[
func blsG1Add*(c: Computation) =
template input: untyped =
c.msg.data
@ -404,7 +414,7 @@ proc blsG1Add*(c: Computation) =
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG1Add encodePoint error")
proc blsG1Mul*(c: Computation) =
func blsG1Mul*(c: Computation) =
template input: untyped =
c.msg.data
@ -458,7 +468,7 @@ func calcBlsMultiExpGas(K: int, gasCost: GasInt): GasInt =
# Calculate gas and return the result
result = (K * gasCost * discount) div 1000
proc blsG1MultiExp*(c: Computation) =
func blsG1MultiExp*(c: Computation) =
template input: untyped =
c.msg.data
@ -499,7 +509,7 @@ proc blsG1MultiExp*(c: Computation) =
if not encodePoint(acc, c.output):
raise newException(ValidationError, "blsG1MuliExp encodePoint error")
proc blsG2Add*(c: Computation) =
func blsG2Add*(c: Computation) =
template input: untyped =
c.msg.data
@ -521,7 +531,7 @@ proc blsG2Add*(c: Computation) =
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG2Add encodePoint error")
proc blsG2Mul*(c: Computation) =
func blsG2Mul*(c: Computation) =
template input: untyped =
c.msg.data
@ -544,7 +554,7 @@ proc blsG2Mul*(c: Computation) =
if not encodePoint(a, c.output):
raise newException(ValidationError, "blsG2Mul encodePoint error")
proc blsG2MultiExp*(c: Computation) =
func blsG2MultiExp*(c: Computation) =
template input: untyped =
c.msg.data
@ -585,7 +595,7 @@ proc blsG2MultiExp*(c: Computation) =
if not encodePoint(acc, c.output):
raise newException(ValidationError, "blsG2MuliExp encodePoint error")
proc blsPairing*(c: Computation) =
func blsPairing*(c: Computation) =
template input: untyped =
c.msg.data
@ -634,7 +644,7 @@ proc blsPairing*(c: Computation) =
if acc.check():
c.output[^1] = 1.byte
proc blsMapG1*(c: Computation) =
func blsMapG1*(c: Computation) =
template input: untyped =
c.msg.data
@ -653,7 +663,7 @@ proc blsMapG1*(c: Computation) =
if not encodePoint(p, c.output):
raise newException(ValidationError, "blsMapG1 encodePoint error")
proc blsMapG2*(c: Computation) =
func blsMapG2*(c: Computation) =
template input: untyped =
c.msg.data
@ -671,8 +681,9 @@ proc blsMapG2*(c: Computation) =
c.output = newSeq[byte](256)
if not encodePoint(p, c.output):
raise newException(ValidationError, "blsMapG2 encodePoint error")
]#
proc pointEvaluation*(c: Computation) =
proc pointEvaluation(c: Computation): EvmResultVoid =
# Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof.
# Also verify that the provided commitment matches the provided versioned_hash.
# The data is encoded as follows: versioned_hash | z | y | commitment | proof |
@ -680,27 +691,43 @@ proc pointEvaluation*(c: Computation) =
template input: untyped =
c.msg.data
c.gasMeter.consumeGas(POINT_EVALUATION_PRECOMPILE_GAS,
reason = "EIP-4844 Point Evaluation Precompile")
? c.gasMeter.consumeGas(POINT_EVALUATION_PRECOMPILE_GAS,
reason = "EIP-4844 Point Evaluation Precompile")
let res = pointEvaluation(input)
if res.isErr:
raise newException(ValidationError, res.error)
pointEvaluation(input).isOkOr:
return err(prcErr(PrcValidationError))
# return a constant
c.output = @PointEvaluationResult
ok()
proc execPrecompiles*(c: Computation, fork: EVMFork): bool {.inline.} =
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
iterator activePrecompiles*(fork: EVMFork): EthAddress =
var res: EthAddress
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
for c in PrecompileAddresses.low..maxPrecompileAddr:
if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
res[^1] = c.byte
yield res
func activePrecompilesList*(fork: EVMFork): seq[EthAddress] =
for address in activePrecompiles(fork):
result.add address
proc execPrecompiles*(c: Computation, fork: EVMFork): bool =
for i in 0..18:
if c.msg.codeAddress[i] != 0: return
if c.msg.codeAddress[i] != 0:
return false
let lb = c.msg.codeAddress[19]
if not validPrecompileAddr(lb, fork):
return
return false
let precompile = PrecompileAddresses(lb)
try:
case precompile
let res = case precompile
of paEcRecover: ecRecover(c)
of paSha256: sha256(c)
of paRipeMd160: ripemd160(c)
@ -723,12 +750,15 @@ proc execPrecompiles*(c: Computation, fork: EVMFork): bool {.inline.} =
# of paBlsPairing: blsPairing(c)
# of paBlsMapG1: blsMapG1(c)
# of paBlsMapG2: blsMapG2(c)
except OutOfGas as e:
c.setError(EVMC_OUT_OF_GAS, e.msg, true)
except CatchableError as e:
if fork >= FkByzantium and precompile > paIdentity:
c.setError(EVMC_PRECOMPILE_FAILURE, e.msg, true)
if res.isErr:
if res.error.code == EvmErrorCode.OutOfGas:
c.setError(EVMC_OUT_OF_GAS, $res.error.code, true)
else:
# swallow any other precompiles errors
debug "execPrecompiles validation error", msg=e.msg
if fork >= FkByzantium and precompile > paIdentity:
c.setError(EVMC_PRECOMPILE_FAILURE, $res.error.code, true)
else:
# swallow any other precompiles errors
debug "execPrecompiles validation error", errCode = $res.error.code
true

View File

@ -1,140 +1,184 @@
# Nimbus
# Copyright (c) 2018-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.
# * 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.
{.push raises: [].}
import
std/[strformat, strutils, sequtils, macros],
chronicles, eth/common,
../errors, ./validation
logScope:
topics = "vm stack"
std/[macros],
eth/common,
./evm_errors,
./interpreter/utils/utils_numeric
type
Stack* = ref object of RootObj
values*: seq[StackElement]
EvmStackRef* = ref object
values: seq[EvmStackElement]
StackElement = UInt256
EvmStackElement = UInt256
EvmStackInts = uint64 | uint | int | GasInt
EvmStackBytes32 = array[32, byte]
template ensureStackLimit: untyped =
if len(stack.values) > 1023:
raise newException(FullStack, "Stack limit reached")
proc len*(stack: Stack): int {.inline.} =
func len*(stack: EvmStackRef): int {.inline.} =
len(stack.values)
proc toStackElement(v: UInt256, elem: var StackElement) {.inline.} = elem = v
proc toStackElement(v: uint64 | uint | int | GasInt, elem: var StackElement) {.inline.} = elem = v.u256
proc toStackElement(v: EthAddress, elem: var StackElement) {.inline.} = elem.initFromBytesBE(v)
proc toStackElement(v: MDigest, elem: var StackElement) {.inline.} = elem.initFromBytesBE(v.data)
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc fromStackElement(elem: StackElement, v: var UInt256) {.inline.} = v = elem
proc fromStackElement(elem: StackElement, v: var EthAddress) {.inline.} = v[0 .. ^1] = elem.toBytesBE().toOpenArray(12, 31)
proc fromStackElement(elem: StackElement, v: var Hash256) {.inline.} = v.data = elem.toBytesBE()
proc fromStackElement(elem: StackElement, v: var Topic) {.inline.} = v = elem.toBytesBE()
template toStackElem(v: UInt256, elem: EvmStackElement) =
elem = v
proc toStackElement(v: openArray[byte], elem: var StackElement) {.inline.} =
# TODO: This needs to go
validateStackItem(v) # This is necessary to pass stack tests
template toStackElem(v: EvmStackInts, elem: EvmStackElement) =
elem = v.u256
template toStackElem(v: EthAddress, elem: EvmStackElement) =
elem.initFromBytesBE(v)
proc pushAux[T](stack: var Stack, value: T) =
ensureStackLimit()
template toStackElem(v: MDigest, elem: EvmStackElement) =
elem.initFromBytesBE(v.data)
template toStackElem(v: openArray[byte], elem: EvmStackElement) =
doAssert(v.len <= 32)
elem.initFromBytesBE(v)
template fromStackElem(elem: EvmStackElement, _: type UInt256): UInt256 =
elem
func fromStackElem(elem: EvmStackElement, _: type EthAddress): EthAddress =
result[0 .. ^1] = elem.toBytesBE().toOpenArray(12, 31)
template fromStackElem(elem: EvmStackElement, _: type Hash256): Hash256 =
Hash256(data: elem.toBytesBE())
template fromStackElem(elem: EvmStackElement, _: type EvmStackBytes32): EvmStackBytes32 =
elem.toBytesBE()
func pushAux[T](stack: EvmStackRef, value: T): EvmResultVoid =
if len(stack.values) > 1023:
return err(stackErr(StackFull))
stack.values.setLen(stack.values.len + 1)
toStackElement(value, stack.values[^1])
toStackElem(value, stack.values[^1])
ok()
proc push*(stack: var Stack, value: uint64 | uint | int | GasInt | UInt256 | EthAddress | Hash256) {.inline.} =
pushAux(stack, value)
func ensurePop(stack: EvmStackRef, expected: int): EvmResultVoid =
if stack.values.len < expected:
return err(stackErr(StackInsufficient))
ok()
proc push*(stack: var Stack, value: openArray[byte]) {.inline.} =
# TODO: This needs to go...
pushAux(stack, value)
proc ensurePop(elements: Stack, a: int) =
let num = elements.len
let expected = a
if num < expected:
raise newException(InsufficientStack,
&"Stack underflow, expect {expected}, got {num}")
proc popAux[T](stack: var Stack, value: var T) =
ensurePop(stack, 1)
fromStackElement(stack.values[^1], value)
func popAux(stack: EvmStackRef, T: type): EvmResult[T] =
? ensurePop(stack, 1)
result = ok(fromStackElem(stack.values[^1], T))
stack.values.setLen(stack.values.len - 1)
proc internalPopTuple(stack: var Stack, v: var tuple, tupleLen: static[int]) =
ensurePop(stack, tupleLen)
var i = 0
func internalPopTuple(stack: EvmStackRef, T: type, tupleLen: static[int]): EvmResult[T] =
? ensurePop(stack, tupleLen)
var
i = 0
v: T
let sz = stack.values.high
for f in fields(v):
fromStackElement(stack.values[sz - i], f)
f = fromStackElem(stack.values[sz - i], UInt256)
inc i
stack.values.setLen(sz - tupleLen + 1)
proc popInt*(stack: var Stack): UInt256 {.inline.} =
popAux(stack, result)
ok(v)
macro genTupleType(len: static[int], elemType: untyped): untyped =
result = nnkTupleConstr.newNimNode()
for i in 0 ..< len: result.add(elemType)
proc popInt*(stack: var Stack, numItems: static[int]): auto {.inline.} =
var r: genTupleType(numItems, UInt256)
stack.internalPopTuple(r, numItems)
return r
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc popAddress*(stack: var Stack): EthAddress {.inline.} =
popAux(stack, result)
func push*(stack: EvmStackRef,
value: EvmStackInts | UInt256 | EthAddress | Hash256): EvmResultVoid =
pushAux(stack, value)
proc popTopic*(stack: var Stack): Topic {.inline.} =
popAux(stack, result)
func push*(stack: EvmStackRef, value: openArray[byte]): EvmResultVoid =
pushAux(stack, value)
proc newStack*(): Stack =
func popInt*(stack: EvmStackRef): EvmResult[UInt256] =
popAux(stack, UInt256)
func popSafeInt*(stack: EvmStackRef): EvmResult[int] =
? ensurePop(stack, 1)
result = ok(fromStackElem(stack.values[^1], UInt256).safeInt)
stack.values.setLen(stack.values.len - 1)
func popMemRef*(stack: EvmStackRef): EvmResult[int] =
? ensurePop(stack, 1)
result = ok(fromStackElem(stack.values[^1], UInt256).cleanMemRef)
stack.values.setLen(stack.values.len - 1)
func popInt*(stack: EvmStackRef, numItems: static[int]): auto =
type T = genTupleType(numItems, UInt256)
stack.internalPopTuple(T, numItems)
func popAddress*(stack: EvmStackRef): EvmResult[EthAddress] =
popAux(stack, EthAddress)
func popTopic*(stack: EvmStackRef): EvmResult[EvmStackBytes32] =
popAux(stack, EvmStackBytes32)
func new*(_: type EvmStackRef): EvmStackRef =
new(result)
result.values = @[]
proc swap*(stack: var Stack, position: int) =
func swap*(stack: EvmStackRef, position: int): EvmResultVoid =
## Perform a SWAP operation on the stack
var idx = position + 1
if idx < len(stack) + 1:
let idx = position + 1
if idx < stack.values.len + 1:
(stack.values[^1], stack.values[^idx]) = (stack.values[^idx], stack.values[^1])
ok()
else:
raise newException(InsufficientStack,
"Stack underflow for SWAP" & $position)
err(stackErr(StackInsufficient))
template getInt(x: int): int = x
proc dup*(stack: var Stack, position: int | UInt256) =
func dup*(stack: EvmStackRef, position: int): EvmResultVoid =
## Perform a DUP operation on the stack
let position = position.getInt
if position in 1 .. stack.len:
stack.push(stack.values[^position])
else:
raise newException(InsufficientStack,
"Stack underflow for DUP" & $position)
err(stackErr(StackInsufficient))
proc peek*(stack: Stack): UInt256 =
# This should be used only for testing purposes!
fromStackElement(stack.values[^1], result)
func peek*(stack: EvmStackRef): EvmResult[UInt256] =
if stack.values.len == 0:
return err(stackErr(StackInsufficient))
ok(fromStackElem(stack.values[^1], UInt256))
proc `$`*(stack: Stack): string =
let values = stack.values.mapIt(&" {$it}").join("\n")
&"Stack:\n{values}"
func peekSafeInt*(stack: EvmStackRef): EvmResult[int] =
if stack.values.len == 0:
return err(stackErr(StackInsufficient))
ok(fromStackElem(stack.values[^1], UInt256).safeInt)
proc `[]`*(stack: Stack, i: BackwardsIndex, T: typedesc): T =
ensurePop(stack, int(i))
fromStackElement(stack.values[i], result)
func `[]`*(stack: EvmStackRef, i: BackwardsIndex, T: typedesc): EvmResult[T] =
? ensurePop(stack, int(i))
ok(fromStackElem(stack.values[i], T))
proc peekInt*(stack: Stack): UInt256 =
ensurePop(stack, 1)
fromStackElement(stack.values[^1], result)
func peekInt*(stack: EvmStackRef): EvmResult[UInt256] =
? ensurePop(stack, 1)
ok(fromStackElem(stack.values[^1], Uint256))
proc peekAddress*(stack: Stack): EthAddress =
ensurePop(stack, 1)
fromStackElement(stack.values[^1], result)
func peekAddress*(stack: EvmStackRef): EvmResult[EthAddress] =
? ensurePop(stack, 1)
ok(fromStackElem(stack.values[^1], EthAddress))
proc top*(stack: Stack, value: uint | int | GasInt | UInt256 | EthAddress | Hash256) {.inline.} =
toStackElement(value, stack.values[^1])
func top*(stack: EvmStackRef,
value: EvmStackInts | UInt256 | EthAddress | Hash256): EvmResultVoid =
if stack.values.len == 0:
return err(stackErr(StackInsufficient))
toStackElem(value, stack.values[^1])
ok()
iterator items*(stack: EvmStackRef): UInt256 =
for v in stack.values:
yield v
iterator pairs*(stack: EvmStackRef): (int, UInt256) =
for i, v in stack.values:
yield (i, v)

View File

@ -17,7 +17,8 @@ import
../db/ledger,
../common/[common, evmforks],
./interpreter/[op_codes, gas_costs],
./types
./types,
./evm_errors
proc init(
self: BaseVMState;
@ -180,16 +181,19 @@ proc new*(
T: type BaseVMState;
header: BlockHeader; ## header with tx environment data fields
com: CommonRef; ## block chain config
tracer: TracerRef = nil): T
{.gcsafe, raises: [CatchableError].} =
tracer: TracerRef = nil): EvmResult[T] =
## This is a variant of the `new()` constructor above where the field
## `header.parentHash`, is used to fetch the `parent` BlockHeader to be
## used in the `new()` variant, above.
BaseVMState.new(
parent = com.db.getBlockHeader(header.parentHash),
header = header,
com = com,
tracer = tracer)
var parent: BlockHeader
if com.db.getBlockHeader(header.parentHash, parent):
ok(BaseVMState.new(
parent = parent,
header = header,
com = com,
tracer = tracer))
else:
err(evmErr(EvmHeaderNotFound))
proc init*(
vmState: BaseVMState;
@ -226,10 +230,16 @@ proc baseFee*(vmState: BaseVMState): UInt256 =
vmState.blockCtx.fee.get(0.u256)
method getAncestorHash*(
vmState: BaseVMState, blockNumber: BlockNumber):
Hash256 {.base, gcsafe, raises: [CatchableError].} =
vmState: BaseVMState, blockNumber: BlockNumber): Hash256 {.base.} =
let db = vmState.com.db
db.getBlockHash(blockNumber)
try:
var blockHash: Hash256
if db.getBlockHash(blockNumber, blockHash):
blockHash
else:
Hash256()
except RlpError:
Hash256()
proc readOnlyStateDB*(vmState: BaseVMState): ReadOnlyStateDB {.inline.} =
ReadOnlyStateDB(vmState.stateDB)

View File

@ -47,8 +47,7 @@ func postExecComputation(c: Computation) =
c.refundSelfDestruct()
c.vmState.status = c.isSuccess
proc execComputation*(c: Computation)
{.gcsafe, raises: [CatchableError].} =
proc execComputation*(c: Computation) =
c.preExecComputation()
c.execCallOrCreate()
c.postExecComputation()

View File

@ -14,7 +14,7 @@ import
".."/[types, stack],
../interpreter/op_codes,
../../db/access_list,
../../errors
../evm_errors
type
AccessListTracer* = ref object of TracerRef
@ -46,26 +46,19 @@ method captureOpStart*(act: AccessListTracer, c: Computation,
fixed: bool, pc: int, op: Op, gas: GasInt,
depth: int): int {.gcsafe.} =
let stackLen = c.stack.len
try:
if (op in [Sload, Sstore]) and (stackLen >= 1):
let slot = c.stack.peekInt()
act.list.add(c.msg.contractAddress, slot)
if (op in [Sload, Sstore]) and (stackLen >= 1):
let slot = c.stack.peekInt().expect("stack is not empty")
act.list.add(c.msg.contractAddress, slot)
if (op in [ExtCodeCopy, ExtCodeHash, ExtCodeSize, Balance, SelfDestruct]) and (stackLen >= 1):
let address = c.stack.peekAddress()
if address notin act.excl:
act.list.add address
if (op in [ExtCodeCopy, ExtCodeHash, ExtCodeSize, Balance, SelfDestruct]) and (stackLen >= 1):
let address = c.stack.peekAddress().expect("stack is not empty")
if address notin act.excl:
act.list.add address
if (op in [DelegateCall, Call, StaticCall, CallCode]) and (stackLen >= 5):
let address = c.stack[^2, EthAddress]
if address notin act.excl:
act.list.add address
except InsufficientStack as exc:
# should not raise, because we already check the stack len
# this try..except block is to prevent unlisted exception error
discard exc
except ValueError as exc:
discard exc
if (op in [DelegateCall, Call, StaticCall, CallCode]) and (stackLen >= 5):
let address = c.stack[^2, EthAddress].expect("stack contains more than 5 elements")
if address notin act.excl:
act.list.add address
# AccessListTracer is not using captureOpEnd
# no need to return op index

View File

@ -13,11 +13,11 @@ import
eth/common/eth_types,
eth/rlp,
stew/byteutils,
results,
chronicles,
".."/[types, memory, stack],
../interpreter/op_codes,
../../db/ledger,
../../errors
../../db/ledger
type
JsonTracer* = ref object of TracerRef
@ -160,15 +160,14 @@ method captureOpStart*(ctx: JsonTracer, c: Computation,
if TracerFlags.DisableStack notin ctx.flags:
ctx.stack = newJArray()
for v in c.stack.values:
for v in c.stack:
ctx.stack.add(%(v.encodeHex))
if TracerFlags.DisableStorage notin ctx.flags and op == Sstore:
try:
if c.stack.values.len > 1:
ctx.rememberStorageKey(c.msg.depth, c.stack[^1, UInt256])
except InsufficientStack as ex:
error "JsonTracer captureOpStart", msg=ex.msg
if c.stack.len > 1:
ctx.rememberStorageKey(c.msg.depth,
c.stack[^1, UInt256].expect("stack constains more than 2 elements"))
except ValueError as ex:
error "JsonTracer captureOpStart", msg=ex.msg

View File

@ -24,7 +24,7 @@ import
".."/[types, memory, stack],
../interpreter/op_codes,
../../db/ledger,
../../errors
../evm_errors
type
LegacyTracer* = ref object of TracerRef
@ -85,7 +85,7 @@ method captureOpStart*(ctx: LegacyTracer, c: Computation,
# log stack
if TracerFlags.DisableStack notin ctx.flags:
let stack = newJArray()
for v in c.stack.values:
for v in c.stack:
stack.add(%v.dumpHex())
j["stack"] = stack
@ -102,26 +102,25 @@ method captureOpStart*(ctx: LegacyTracer, c: Computation,
if TracerFlags.EnableAccount in ctx.flags:
case op
of Call, CallCode, DelegateCall, StaticCall:
if c.stack.values.len > 2:
ctx.accounts.incl c.stack[^2, EthAddress]
if c.stack.len > 2:
ctx.accounts.incl c.stack[^2, EthAddress].expect("stack constains more than 2 elements")
of ExtCodeCopy, ExtCodeSize, Balance, SelfDestruct:
if c.stack.values.len > 1:
ctx.accounts.incl c.stack[^1, EthAddress]
if c.stack.len > 1:
ctx.accounts.incl c.stack[^1, EthAddress].expect("stack is not empty")
else:
discard
if TracerFlags.DisableStorage notin ctx.flags:
if op == Sstore:
if c.stack.values.len > 1:
ctx.rememberStorageKey(c.msg.depth, c.stack[^1, UInt256])
if c.stack.len > 1:
ctx.rememberStorageKey(c.msg.depth,
c.stack[^1, UInt256].expect("stack is not empty"))
result = ctx.trace["structLogs"].len - 1
except KeyError as ex:
error "LegacyTracer captureOpStart", msg=ex.msg
except ValueError as ex:
error "LegacyTracer captureOpStart", msg=ex.msg
except InsufficientStack as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
method captureOpEnd*(ctx: LegacyTracer, c: Computation,
fixed: bool, pc: int, op: Op, gas: GasInt, refund: GasInt,
@ -166,8 +165,6 @@ method captureFault*(ctx: LegacyTracer, comp: Computation,
ctx.trace["failed"] = %true
except KeyError as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
except InsufficientStack as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
proc getTracingResult*(ctx: LegacyTracer): JsonNode =
ctx.trace

View File

@ -9,9 +9,7 @@
# according to those terms.
import
chronos,
json_rpc/rpcclient,
"."/[stack, memory, code_stream],
"."/[stack, memory, code_stream, evm_errors],
./interpreter/[gas_costs, op_codes],
../db/ledger,
../common/[common, evmforks]
@ -73,8 +71,8 @@ type
# The execution computation
vmState*: BaseVMState
msg*: Message
memory*: Memory
stack*: Stack
memory*: EvmMemoryRef
stack*: EvmStackRef
returnStack*: seq[int]
gasMeter*: GasMeter
code*: CodeStream
@ -90,8 +88,7 @@ type
res*: nimbus_result
else:
parent*, child*: Computation
pendingAsyncOperation*: Future[void]
continuation*: proc() {.gcsafe, raises: [CatchableError].}
continuation*: proc(): EvmResultVoid {.gcsafe, raises: [].}
sysCall*: bool
Error* = ref object

View File

@ -1,50 +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.
import
strformat,
../errors, eth/common
proc validateGte*(value: Int256 | int, minimum: int, title: string = "Value") =
if value.i256 < minimum.i256:
raise newException(ValidationError,
&"{title} {value} is not greater than or equal to {minimum}")
proc validateGt*(value: Int256 | int, minimum: int, title: string = "Value") =
if value.i256 <= minimum.i256:
raise newException(ValidationError,
&"{title} {value} is not greater than {minimum}")
proc validateLength*[T](values: seq[T], size: int) =
if values.len != size:
raise newException(ValidationError,
&"seq expected {size} len, got {values.len}")
proc validateLte*(value: UInt256 | int, maximum: int, title: string = "Value") =
if value.u256 > maximum.u256:
raise newException(ValidationError,
&"{title} {value} is not less or equal to {maximum}")
proc validateLt*(value: UInt256 | int, maximum: int, title: string = "Value") =
if value.u256 >= maximum.u256:
raise newException(ValidationError,
&"{title} {value} is not less than {maximum}")
proc validateLte*(value: int, maximum: int, title: string = "Value") =
if value > maximum:
raise newException(ValidationError,
&"{title} {value} is not less or equal to {maximum}")
proc validateStackItem*(value: string) =
if value.len > 32:
raise newException(ValidationError,
&"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}")
proc validateStackItem*(value: openArray[byte]) =
if value.len > 32:
raise newException(ValidationError,
&"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}")

View File

@ -1057,8 +1057,9 @@ proc toTxArgs(n: Node): TransactionArgs {.gcsafe, raises: [ValueError].} =
optionalBytes(result.data, n, fData)
proc makeCall(ctx: GraphqlContextRef, args: TransactionArgs,
header: common.BlockHeader): RespResult {.gcsafe, raises: [CatchableError].} =
let res = rpcCallEvm(args, header, ctx.com)
header: common.BlockHeader): RespResult =
let res = rpcCallEvm(args, header, ctx.com).valueOr:
return err("Failed to call rpcCallEvm")
var map = respMap(ctx.ids[ethCallResult])
map["data"] = resp("0x" & res.output.toHex)
map["gasUsed"] = longNode(res.gasUsed).get()
@ -1084,7 +1085,8 @@ proc blockEstimateGas(ud: RootRef, params: Args, parent: Node): RespResult {.api
let args = toTxArgs(param)
# TODO: DEFAULT_RPC_GAS_CAP should configurable
{.cast(noSideEffect).}:
let gasUsed = rpcEstimateGas(args, h.header, ctx.com, DEFAULT_RPC_GAS_CAP)
let gasUsed = rpcEstimateGas(args, h.header, ctx.com, DEFAULT_RPC_GAS_CAP).valueOr:
return err("Failed to call rpcEstimateGas")
longNode(gasUsed)
except CatchableError as em:
err("estimateGas error: " & em.msg)

View File

@ -42,7 +42,8 @@ proc getBlockWitness*(
# Initializing the VM will throw a Defect if the state doesn't exist.
# Once we enable pruning we will need to check if the block state has been pruned
# before trying to initialize the VM as we do here.
vmState = BaseVMState.new(blockHeader, com)
vmState = BaseVMState.new(blockHeader, com).valueOr:
raise newException(ValueError, "Cannot create vm state")
vmState.generateWitness = true # Enable saving witness data
vmState.com.hardForkTransition(blockHeader)
@ -60,7 +61,8 @@ proc getBlockWitness*(
result = (mkeys, vmState.buildWitness(mkeys))
else:
# Use the initial state from prior to executing the block of transactions
let initialState = BaseVMState.new(blockHeader, com)
let initialState = BaseVMState.new(blockHeader, com).valueOr:
raise newException(ValueError, "Cannot create vm state")
result = (mkeys, initialState.buildWitness(mkeys))
dbTx.rollback()

View File

@ -24,6 +24,7 @@ import
../common/[common, context],
../utils/utils,
../beacon/web3_eth_conv,
../evm/evm_errors,
./filters
const
@ -352,7 +353,8 @@ proc setupEthRpc*(
## Returns the return value of executed contract.
let
header = headerFromTag(chainDB, quantityTag)
res = rpcCallEvm(args, header, com)
res = rpcCallEvm(args, header, com).valueOr:
raise newException(ValueError, "rpcCallEvm error: " & $error.code)
result = res.output
server.rpc("eth_estimateGas") do(args: TransactionArgs) -> Web3Quantity:
@ -366,7 +368,8 @@ proc setupEthRpc*(
let
header = chainDB.headerFromTag(blockId("latest"))
# TODO: DEFAULT_RPC_GAS_CAP should configurable
gasUsed = rpcEstimateGas(args, header, com, DEFAULT_RPC_GAS_CAP)
gasUsed = rpcEstimateGas(args, header, com, DEFAULT_RPC_GAS_CAP).valueOr:
raise newException(ValueError, "rpcEstimateGas error: " & $error.code)
result = w3Qty(gasUsed)
server.rpc("eth_getBlockByHash") do(data: Web3Hash, fullTransactions: bool) -> BlockObject:

View File

@ -15,6 +15,7 @@ import
../transaction/call_common,
../vm_types,
../beacon/web3_eth_conv,
../evm/evm_errors,
./rpc_types
export
@ -32,14 +33,12 @@ func destination*(args: TransactionArgs): EthAddress =
proc toCallParams*(vmState: BaseVMState, args: TransactionArgs,
globalGasCap: GasInt, baseFee: Option[UInt256],
forkOverride = none(EVMFork)): CallParams
{.gcsafe, raises: [ValueError].} =
forkOverride = none(EVMFork)): EvmResult[CallParams] =
# Reject invalid combinations of pre- and post-1559 fee styles
if args.gasPrice.isSome and
(args.maxFeePerGas.isSome or args.maxPriorityFeePerGas.isSome):
raise newException(ValueError,
"both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
return err(evmErr(EvmInvalidParam))
# Set default gas & gas price if none were set
var gasLimit = globalGasCap
@ -73,7 +72,7 @@ proc toCallParams*(vmState: BaseVMState, args: TransactionArgs,
else:
@[]
CallParams(
ok(CallParams(
vmState: vmState,
forkOverride: forkOverride,
sender: args.sender,
@ -85,6 +84,6 @@ proc toCallParams*(vmState: BaseVMState, args: TransactionArgs,
input: args.payload(),
accessList: ethAccessList args.accessList,
versionedHashes: args.versionedHashes,
)
))
{.pop.}

View File

@ -25,7 +25,8 @@ import
../vm_types,
../vm_state,
../evm/precompiles,
../evm/tracer/access_list_tracer
../evm/tracer/access_list_tracer,
../evm/evm_errors
const
@ -283,7 +284,13 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
proc createAccessList*(header: BlockHeader,
com: CommonRef,
args: TransactionArgs): AccessListResult {.gcsafe, raises:[CatchableError].} =
args: TransactionArgs): AccessListResult =
template handleError(msg: string) =
return AccessListResult(
error: some(msg),
)
var args = args
# If the gas amount is not set, default to RPC gas cap.
@ -291,7 +298,8 @@ proc createAccessList*(header: BlockHeader,
args.gas = some(Quantity DEFAULT_RPC_GAS_CAP)
let
vmState = BaseVMState.new(header, com)
vmState = BaseVMState.new(header, com).valueOr:
handleError("failed to create vmstate: " & $error.code)
fork = com.toEVMFork(forkDeterminationInfo(header.blockNumber, header.timestamp))
sender = args.sender
# TODO: nonce should be retrieved from txPool
@ -318,13 +326,13 @@ proc createAccessList*(header: BlockHeader,
# Apply the transaction with the access list tracer
let
tracer = AccessListTracer.new(accessList, sender, to, precompiles)
vmState = BaseVMState.new(header, com, tracer)
res = rpcCallEvm(args, header, com, vmState)
vmState = BaseVMState.new(header, com, tracer).valueOr:
handleError("failed to create vmstate: " & $error.code)
res = rpcCallEvm(args, header, com, vmState).valueOr:
handleError("failed to call evm: " & $error.code)
if res.isError:
return AccessListResult(
error: some("failed to apply transaction: " & res.error),
)
handleError("failed to apply transaction: " & res.error)
if tracer.equal(prevTracer):
return AccessListResult(

View File

@ -119,8 +119,10 @@ proc traceTransaction*(com: CommonRef, header: BlockHeader,
captureCom = com.clone(capture.recorder)
saveCtx = setCtx com.newCtx(com.db.getParentHeader(header).stateRoot)
vmState = BaseVMState.new(header, captureCom)
vmState = BaseVMState.new(header, captureCom).valueOr:
return newJNull()
stateDb = vmState.stateDB
defer:
saveCtx.setCtx().ctx.forget()
capture.forget()
@ -199,7 +201,8 @@ proc dumpBlockState*(com: CommonRef, header: BlockHeader, body: BlockBody, dumpS
tracerInst = newLegacyTracer(captureFlags)
saveCtx = setCtx com.newCtx(parent.stateRoot)
vmState = BaseVMState.new(header, captureCom, tracerInst)
vmState = BaseVMState.new(header, captureCom, tracerInst).valueOr:
return newJNull()
miner = vmState.coinbase()
defer:
saveCtx.setCtx().ctx.forget()
@ -258,7 +261,9 @@ proc traceBlock*(com: CommonRef, header: BlockHeader, body: BlockBody, tracerFla
tracerInst = newLegacyTracer(tracerFlags)
saveCtx = setCtx com.newCtx(com.db.getParentHeader(header).stateRoot)
vmState = BaseVMState.new(header, captureCom, tracerInst)
vmState = BaseVMState.new(header, captureCom, tracerInst).valueOr:
return newJNull()
defer:
saveCtx.setCtx().ctx.forget()
capture.forget()

View File

@ -11,7 +11,7 @@
import
eth/common/eth_types, stint, options, stew/ptrops,
chronos,
".."/[vm_types, vm_state, vm_computation, vm_state_transactions],
".."/[vm_types, vm_state, vm_computation],
".."/[vm_internals, vm_precompiles, vm_gas_costs],
".."/[db/ledger],
../common/evmforks,
@ -21,6 +21,8 @@ import
when defined(evmc_enabled):
import ../utils/utils
import ./host_services
else:
import ../vm_state_transactions
type
# Standard call parameters.
@ -51,8 +53,8 @@ type
contractAddress*: EthAddress # Created account (when `isCreate`).
output*: seq[byte] # Output data.
logEntries*: seq[Log] # Output logs.
stack*: Stack # EVM stack on return (for test only).
memory*: Memory # EVM memory on return (for test only).
stack*: EvmStackRef # EVM stack on return (for test only).
memory*: EvmMemoryRef # EVM memory on return (for test only).
func isError*(cr: CallResult): bool =
cr.error.len > 0
@ -204,8 +206,7 @@ proc setupHost(call: CallParams): TransactionHost =
return host
when defined(evmc_enabled):
proc doExecEvmc(host: TransactionHost, call: CallParams)
{.gcsafe, raises: [CatchableError].} =
proc doExecEvmc(host: TransactionHost, call: CallParams) =
var callResult = evmcExecComputation(host)
let c = host.computation
@ -291,8 +292,7 @@ proc finishRunningComputation(host: TransactionHost, call: CallParams): CallResu
result.stack = c.stack
result.memory = c.memory
proc runComputation*(call: CallParams): CallResult
{.gcsafe, raises: [CatchableError].} =
proc runComputation*(call: CallParams): CallResult =
let host = setupHost(call)
prepareToRunComputation(host, call)

View File

@ -16,55 +16,55 @@ import
".."/[vm_types, vm_state, vm_gas_costs],
../db/ledger,
../common/common,
../evm/evm_errors,
../rpc/params,
./call_common
export
call_common
proc rpcCallEvm*(args: TransactionArgs, header: common.BlockHeader, com: CommonRef): CallResult
{.gcsafe, raises: [CatchableError].} =
proc rpcCallEvm*(args: TransactionArgs,
header: common.BlockHeader,
com: CommonRef): EvmResult[CallResult] =
const globalGasCap = 0 # TODO: globalGasCap should configurable by user
let topHeader = common.BlockHeader(
parentHash: header.blockHash,
timestamp: EthTime.now(),
gasLimit: 0.GasInt, ## ???
fee: UInt256.none()) ## ???
let vmState = BaseVMState.new(topHeader, com)
let params = toCallParams(vmState, args, globalGasCap, header.fee)
let vmState = ? BaseVMState.new(topHeader, com)
let params = ? toCallParams(vmState, args, globalGasCap, header.fee)
var dbTx = com.db.newTransaction()
defer: dbTx.dispose() # always dispose state changes
runComputation(params)
ok(runComputation(params))
proc rpcCallEvm*(args: TransactionArgs,
header: common.BlockHeader,
com: CommonRef,
vmState: BaseVMState): CallResult
{.gcsafe, raises: [CatchableError].} =
vmState: BaseVMState): EvmResult[CallResult] =
const globalGasCap = 0 # TODO: globalGasCap should configurable by user
let params = toCallParams(vmState, args, globalGasCap, header.fee)
let params = ? toCallParams(vmState, args, globalGasCap, header.fee)
var dbTx = com.db.newTransaction()
defer: dbTx.dispose() # always dispose state changes
runComputation(params)
ok(runComputation(params))
proc rpcEstimateGas*(args: TransactionArgs,
header: common.BlockHeader,
com: CommonRef, gasCap: GasInt): GasInt
{.gcsafe, raises: [CatchableError].} =
com: CommonRef, gasCap: GasInt): EvmResult[GasInt] =
# Binary search the gas requirement, as it may be higher than the amount used
let topHeader = common.BlockHeader(
parentHash: header.blockHash,
timestamp: EthTime.now(),
gasLimit: 0.GasInt, ## ???
fee: UInt256.none()) ## ???
let vmState = BaseVMState.new(topHeader, com)
let vmState = ? BaseVMState.new(topHeader, com)
let fork = vmState.determineFork
let txGas = gasFees[fork][GasTransaction] # txGas always 21000, use constants?
var params = toCallParams(vmState, args, gasCap, header.fee)
var params = ? toCallParams(vmState, args, gasCap, header.fee)
var
lo : GasInt = txGas - 1
@ -83,22 +83,21 @@ proc rpcEstimateGas*(args: TransactionArgs,
var feeCap = GasInt args.gasPrice.get(0.Quantity)
if args.gasPrice.isSome and
(args.maxFeePerGas.isSome or args.maxPriorityFeePerGas.isSome):
raise newException(ValueError,
"both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
return err(evmErr(EvmInvalidParam))
elif args.maxFeePerGas.isSome:
feeCap = GasInt args.maxFeePerGas.get
# Recap the highest gas limit with account's available balance.
if feeCap > 0:
if args.source.isNone:
raise newException(ValueError, "`from` can't be null")
return err(evmErr(EvmInvalidParam))
let balance = vmState.readOnlyStateDB.getBalance(ethAddr args.source.get)
var available = balance
if args.value.isSome:
let value = args.value.get
if value > available:
raise newException(ValueError, "insufficient funds for transfer")
return err(evmErr(EvmInvalidParam))
available -= value
let allowance = available div feeCap.u256
@ -118,20 +117,20 @@ proc rpcEstimateGas*(args: TransactionArgs,
let intrinsicGas = intrinsicGas(params, vmState)
# Create a helper to check if a gas allowance results in an executable transaction
proc executable(gasLimit: GasInt): bool
{.gcsafe, raises: [CatchableError].} =
proc executable(gasLimit: GasInt): EvmResult[bool] =
if intrinsicGas > gasLimit:
# Special case, raise gas limit
return true
return ok(true)
params.gasLimit = gasLimit
# TODO: bail out on consensus error similar to validateTransaction
runComputation(params).isError
let res = runComputation(params)
ok(res.isError)
# Execute the binary search and hone in on an executable gas limit
while lo+1 < hi:
let mid = (hi + lo) div 2
let failed = executable(mid)
let failed = ? executable(mid)
if failed:
lo = mid
else:
@ -139,13 +138,13 @@ proc rpcEstimateGas*(args: TransactionArgs,
# Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap:
let failed = executable(hi)
let failed = ? executable(hi)
if failed:
# TODO: provide more descriptive EVM error beside out of gas
# e.g. revert and other EVM errors
raise newException(ValueError, "gas required exceeds allowance " & $cap)
return err(evmErr(EvmInvalidParam))
hi
ok(hi)
proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams =
# Is there a nice idiom for this kind of thing? Should I
@ -188,12 +187,18 @@ proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState
if tx.txType >= TxEip4844:
result.versionedHashes = tx.versionedHashes
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt
{.gcsafe, raises: [CatchableError].} =
let call = callParamsForTx(tx, sender, vmState, fork)
return runComputation(call).gasUsed
proc txCallEvm*(tx: Transaction,
sender: EthAddress,
vmState: BaseVMState,
fork: EVMFork): GasInt =
let
call = callParamsForTx(tx, sender, vmState, fork)
res = runComputation(call)
res.gasUsed
proc testCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallResult
{.gcsafe, raises: [CatchableError].} =
proc testCallEvm*(tx: Transaction,
sender: EthAddress,
vmState: BaseVMState,
fork: EVMFork): CallResult =
let call = callParamsForTest(tx, sender, vmState, fork)
runComputation(call)

View File

@ -83,7 +83,7 @@ proc evmcLoadVMGetCreateFn(): (evmc_create_vm_name_fn, string) =
return (cast[evmc_create_vm_name_fn](sym), path)
proc evmcLoadVMShowDetail(): ptr evmc_vm {.raises: [].} =
proc evmcLoadVMShowDetail(): ptr evmc_vm =
let (vmCreate, vmDescription) = evmcLoadVMGetCreateFn()
if vmCreate.isNil:
return nil
@ -109,7 +109,7 @@ proc evmcLoadVMShowDetail(): ptr evmc_vm {.raises: [].} =
info "Using EVM", name=name, version=version, `from`=vmDescription
return vm
proc evmcLoadVMCached*(): ptr evmc_vm {.raises: [CatchableError].} =
proc evmcLoadVMCached*(): ptr evmc_vm =
# TODO: Make this open the VM library once per process. Currently it does
# so once per thread, but at least this is thread-safe.
var vm {.threadvar.}: ptr evmc_vm

View File

@ -21,12 +21,12 @@ proc accountExists(p: evmc_host_context, address: var evmc_address): c99bool {.c
proc getStorage(p: evmc_host_context, address: var evmc_address,
key: var evmc_bytes32): evmc_bytes32
{.cdecl, raises: [].} =
{.cdecl.} =
toHost(p).getStorage(address.fromEvmc, key.flip256.fromEvmc).toEvmc.flip256
proc setStorage(p: evmc_host_context, address: var evmc_address,
key, value: var evmc_bytes32): evmc_storage_status
{.cdecl, raises: [].} =
{.cdecl.} =
toHost(p).setStorage(address.fromEvmc, key.flip256.fromEvmc, value.flip256.fromEvmc)
proc getBalance(p: evmc_host_context,
@ -50,7 +50,7 @@ proc selfDestruct(p: evmc_host_context, address,
toHost(p).selfDestruct(address.fromEvmc, beneficiary.fromEvmc)
proc call(p: evmc_host_context, msg: var evmc_message): evmc_result
{.cdecl, raises: [CatchableError].} =
{.cdecl.} =
# This would contain `flip256`, but `call` is special. The C stack usage
# must be kept small for deeply nested EVM calls. To ensure small stack,
# `flip256` must be handled at `host_call_nested`, not here.
@ -62,7 +62,7 @@ proc getTxContext(p: evmc_host_context): evmc_tx_context {.cdecl.} =
toHost(p).getTxContext()
proc getBlockHash(p: evmc_host_context, number: int64): evmc_bytes32
{.cdecl, raises: [CatchableError].} =
{.cdecl.} =
# TODO: `HostBlockNumber` is 256-bit unsigned. It should be changed to match
# EVMC which is more sensible.
toHost(p).getBlockHash(number.uint64.u256).toEvmc
@ -83,11 +83,11 @@ proc accessStorage(p: evmc_host_context, address: var evmc_address,
proc getTransientStorage(p: evmc_host_context, address: var evmc_address,
key: var evmc_bytes32): evmc_bytes32
{.cdecl, raises: [].} =
{.cdecl.} =
toHost(p).getTransientStorage(address.fromEvmc, key.flip256.fromEvmc).toEvmc.flip256
proc setTransientStorage(p: evmc_host_context, address: var evmc_address,
key, value: var evmc_bytes32) {.cdecl, raises: [].} =
key, value: var evmc_bytes32) {.cdecl.} =
toHost(p).setTransientStorage(address.fromEvmc, key.flip256.fromEvmc, value.flip256.fromEvmc)
let hostInterface = evmc_host_interface(
@ -109,8 +109,7 @@ let hostInterface = evmc_host_interface(
set_transient_storage: setTransientStorage,
)
proc evmcExecComputation*(host: TransactionHost): EvmcResult
{.raises: [CatchableError].} =
proc evmcExecComputation*(host: TransactionHost): EvmcResult =
host.showCallEntry(host.msg)
let vm = evmcLoadVMCached()

View File

@ -38,13 +38,10 @@ proc evmcExecute(vm: ptr evmc_vm, hostInterface: ptr evmc_host_interface,
# host.computation = c
c.host.init(cast[ptr nimbus_host_interface](hostInterface), hostContext)
try:
if c.sysCall:
execSysCall(c)
else:
execComputation(c)
except CatchableError as exc:
c.setError(exc.msg)
if c.sysCall:
execSysCall(c)
else:
execComputation(c)
# When output size is zero, output data pointer may be null.
var output_data: ptr byte

View File

@ -112,7 +112,7 @@ proc afterExecCallEvmcNested(host: TransactionHost, child: Computation,
proc beforeExecEvmcNested(host: TransactionHost, msg: EvmcMessage): Computation
# This function must be declared with `{.noinline.}` to make sure it doesn't
# contribute to the stack frame of `callEvmcNested` below.
{.noinline, gcsafe, raises: [ValueError].} =
{.noinline.} =
# `call` is special. Most host functions do `flip256` in `evmc_host_glue`
# and `show` in `host_services`, but `call` needs to minimise C stack used
# by nested EVM calls. Just `flip256` in glue's `call` adds a lot of
@ -136,7 +136,7 @@ proc afterExecEvmcNested(host: TransactionHost, child: Computation,
kind: EvmcCallKind): EvmcResult
# This function must be declared with `{.noinline.}` to make sure it doesn't
# contribute to the stack frame of `callEvmcNested` below.
{.noinline, gcsafe, raises: [ValueError].} =
{.noinline.} =
host.computation = host.saveComputation[^1]
host.saveComputation[^1] = nil
host.saveComputation.setLen(host.saveComputation.len - 1)

View File

@ -1,6 +1,6 @@
# Nimbus - Trace EVMC host calls when EVM code is run for a transaction
#
# Copyright (c) 2021 Status Research & Development GmbH
# 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)
@ -9,7 +9,7 @@
{.push raises: [].}
import
macros, strformat, strutils, stint, chronicles,
macros, strutils, stint, chronicles,
stew/byteutils, stew/ptrops,
./host_types
@ -44,34 +44,32 @@ proc depthPrefix(host: TransactionHost): string =
let num = '(' & $depth & ')'
return num & spaces(42 - num.len)
proc showEvmcMessage(msg: EvmcMessage): string
{.gcsafe, raises: [ValueError].} =
proc showEvmcMessage(msg: EvmcMessage): string =
let kindStr =
if msg.flags == {}: $msg.kind
elif msg.flags == {EVMC_STATIC} and msg.kind == EVMC_CALL: "CALL_STATIC"
else: &"{$msg.kind} flags={$msg.flags}"
else: $msg.kind & " flags=" & $msg.flags
var inputStr = "(" & $msg.input_size & ")"
if msg.input_size > 0:
inputStr.add toHex(makeOpenArray(msg.input_data,
min(msg.input_size, 256).int))
if msg.input_size > 256:
inputStr.add "..."
result = &"kind={kindStr}" &
&" depth={$msg.depth}" &
&" gas={$msg.gas}" &
&" value={$msg.value.fromEvmc}" &
&" sender={$msg.sender.fromEvmc}" &
&" recipient={$msg.recipient.fromEvmc}" &
&" code_address={$msg.code_address.fromEvmc}" &
&" input_data={inputStr}"
result = "kind=" & $kindStr &
" depth=" & $msg.depth &
" gas=" & $msg.gas &
" value=" & $msg.value.fromEvmc &
" sender=" & $msg.sender.fromEvmc &
" recipient=" & $msg.recipient.fromEvmc &
" code_address=" & $msg.code_address.fromEvmc &
" input_data=" & inputStr
if msg.kind == EVMC_CREATE2:
result.add &" create2_salt={$msg.create2_salt.fromEvmc}"
result.add " create2_salt=" & $msg.create2_salt.fromEvmc
proc showEvmcResult(res: EvmcResult, withCreateAddress = true): string
{.gcsafe, raises: [ValueError].} =
proc showEvmcResult(res: EvmcResult, withCreateAddress = true): string =
if res.status_code != EVMC_SUCCESS and res.status_code != EVMC_REVERT and
res.gas_left == 0 and res.output_size == 0:
return &"status={$res.status_code}"
return "status=" & $res.status_code
var outputStr = "(" & $res.output_size & ")"
if res.output_size > 0:
@ -80,23 +78,22 @@ proc showEvmcResult(res: EvmcResult, withCreateAddress = true): string
if res.output_size > 256:
outputStr.add "..."
result = &"status={$res.status_code}" &
&" gas_left={$res.gas_left}" &
&" output_data={outputStr}"
result = "status=" & $res.status_code &
" gas_left=" & $res.gas_left &
" output_data=" & $outputStr
if withCreateAddress:
result.add &" create_address={$res.create_address.fromEvmc}"
result.add " create_address=" & $res.create_address.fromEvmc
proc showEvmcTxContext(txc: EvmcTxContext): string
{.gcsafe, raises: [ValueError].} =
return &"tx_gas_price={$txc.tx_gas_price.fromEvmc}" &
&" tx_origin={$txc.tx_origin.fromEvmc}" &
&" block_coinbase={$txc.block_coinbase.fromEvmc}" &
&" block_number={$txc.block_number}" &
&" block_timestamp={$txc.block_timestamp}" &
&" block_gas_limit={$txc.block_gas_limit}" &
&" block_prev_randao={$txc.block_prev_randao.fromEvmc}" &
&" chain_id={$txc.chain_id.fromEvmc}" &
&" block_base_fee={$txc.block_base_fee.fromEvmc}"
proc showEvmcTxContext(txc: EvmcTxContext): string =
return "tx_gas_price=" & $txc.tx_gas_price.fromEvmc &
" tx_origin=" & $txc.tx_origin.fromEvmc &
" block_coinbase=" & $txc.block_coinbase.fromEvmc &
" block_number=" & $txc.block_number &
" block_timestamp=" & $txc.block_timestamp &
" block_gas_limit=" & $txc.block_gas_limit &
" block_prev_randao=" & $txc.block_prev_randao.fromEvmc &
" chain_id=" & $txc.chain_id.fromEvmc &
" block_base_fee=" & $txc.block_base_fee.fromEvmc
proc showEvmcArgsExpr(fn: NimNode, callName: string): auto =
var args: seq[NimNode] = newSeq[NimNode]()

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -14,10 +14,10 @@ import
./evm/memory as vmm
export
vmm.Memory,
vmm.EvmMemoryRef,
vmm.extend,
vmm.len,
vmm.newMemory,
vmm.new,
vmm.read,
vmm.write
@ -116,12 +116,11 @@ export
fVmo.Op,
fVmo.PrevRandao,
gVmg.isCreate,
hStk.Stack,
hStk.`$`,
hStk.EvmStackRef,
hStk.`[]`,
hStk.dup,
hStk.len,
hStk.newStack,
hStk.new,
hStk.peek,
hStk.peekInt,
hStk.popAddress,
@ -129,6 +128,14 @@ export
hStk.popTopic,
hStk.push,
hStk.swap,
hStk.top
hStk.top,
hStk.items,
hStk.pairs
import
./evm/evm_errors
export
evm_errors
# End

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -14,24 +14,6 @@ import
export
vmp.PrecompileAddresses,
vmp.activePrecompiles,
vmp.blake2bf,
vmp.blsG1Add,
vmp.blsG1Mul,
vmp.blsG1MultiExp,
vmp.blsG2Add,
vmp.blsG2Mul,
vmp.blsG2MultiExp,
vmp.blsMapG1,
vmp.blsMapG2,
vmp.blsPairing,
vmp.bn256ecAdd,
vmp.bn256ecMul,
vmp.ecRecover,
vmp.execPrecompiles,
vmp.identity,
vmp.modExp,
vmp.ripemd160,
vmp.sha256,
vmp.simpleDecode
vmp.execPrecompiles
# End

View File

@ -82,7 +82,7 @@ proc new(T: type HunterVMState; parent, header: BlockHeader, com: CommonRef): T
result.init(parent, header, com)
result.headers = initTable[BlockNumber, BlockHeader]()
method getAncestorHash*(vmState: HunterVMState, blockNumber: BlockNumber): Hash256 {.gcsafe.} =
method getAncestorHash*(vmState: HunterVMState, blockNumber: BlockNumber): Hash256 =
if blockNumber in vmState.headers:
result = vmState.headers[blockNumber].hash
else:

View File

@ -34,14 +34,6 @@ import ../tools/common/helpers except LogLevel
export byteutils
{.experimental: "dynamicBindSym".}
# backported from Nim 0.19.9
# remove this when we use newer Nim
#proc newLitFixed*(arg: enum): NimNode {.compileTime.} =
# result = newCall(
# arg.type.getTypeInst[1],
# newLit(int(arg))
# )
type
VMWord* = array[32, byte]
Storage* = tuple[key, val: VMWord]
@ -308,11 +300,11 @@ proc verifyAsmResult(vmState: BaseVMState, boa: Assembler, asmResult: CallResult
error "different gasUsed", expected=boa.gasUsed, actual=asmResult.gasUsed
return false
if boa.stack.len != asmResult.stack.values.len:
error "different stack len", expected=boa.stack.len, actual=asmResult.stack.values.len
if boa.stack.len != asmResult.stack.len:
error "different stack len", expected=boa.stack.len, actual=asmResult.stack.len
return false
for i, v in asmResult.stack.values:
for i, v in asmResult.stack:
let actual = v.dumpHex()
let val = boa.stack[i].toHex()
if actual != val:

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-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)
@ -8,7 +8,7 @@
import
unittest2, macros, strformat,
eth/common/eth_types,
../nimbus/[vm_types, errors, vm_internals]
../nimbus/[vm_types, vm_internals]
# TODO: quicktest
# PS: parametrize can be easily immitated, but still quicktests would be even more useful
@ -82,20 +82,19 @@ proc gasMeterMain*() =
all(gasMeter):
check(gasMeter.gasRemaining == StartGas)
let consume = StartGas
gasMeter.consumeGas(consume, "0")
check gasMeter.consumeGas(consume, "0").isOk
check(gasMeter.gasRemaining - (StartGas - consume) == 0)
test "consume errors":
all(gasMeter):
check(gasMeter.gasRemaining == StartGas)
expect(OutOfGas):
gasMeter.consumeGas(StartGas + 1, "")
check gasMeter.consumeGas(StartGas + 1, "").error.code == EvmErrorCode.OutOfGas
test "return refund works correctly":
all(gasMeter):
check(gasMeter.gasRemaining == StartGas)
check(gasMeter.gasRefunded == 0)
gasMeter.consumeGas(5, "")
check gasMeter.consumeGas(5, "").isOk
check(gasMeter.gasRemaining == StartGas - 5)
gasMeter.returnGas(5)
check(gasMeter.gasRemaining == StartGas)

View File

@ -46,13 +46,13 @@ proc toBytes(x: string): seq[byte] =
result = newSeq[byte](x.len)
for i in 0..<x.len: result[i] = x[i].byte
method getAncestorHash*(vmState: BaseVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
method getAncestorHash*(vmState: BaseVMState; blockNumber: BlockNumber): Hash256 =
if blockNumber >= vmState.blockNumber:
return
return Hash256()
elif blockNumber < 0:
return
return Hash256()
elif blockNumber < vmState.blockNumber - 256:
return
return Hash256()
else:
return keccakHash(toBytes($blockNumber))

View File

@ -6,52 +6,32 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
unittest2, sequtils,
../nimbus/[errors, vm_internals]
std/sequtils,
unittest2,
../nimbus/evm/evm_errors,
../nimbus/evm/memory
proc memory32: Memory =
result = newMemory()
result.extend(0, 32)
proc memory32: EvmMemoryRef =
result = EvmMemoryRef.new(32)
proc memory128: Memory =
result = newMemory()
result.extend(0, 128)
proc memory128: EvmMemoryRef =
result = EvmMemoryRef.new(123)
proc memoryMain*() =
suite "memory":
test "write":
var mem = memory32()
# Test that write creates 32byte string == value padded with zeros
mem.write(startPos = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check mem.write(startPos = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte]).isOk
check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28)))
# test "write rejects invalid position":
# expect(ValidationError):
# var mem = memory32()
# mem.write(startPosition = -1.i256, size = 2.i256, value = @[1.byte, 0.byte])
# expect(ValidationError):
# TODO: work on 256
# var mem = memory32()
# echo "pow ", pow(2.i256, 255) - 1.i256
# mem.write(startPosition = pow(2.i256, 256), size = 2.i256, value = @[1.byte, 0.byte])
# test "write rejects invalid size":
# # expect(ValidationError):
# # var mem = memory32()
# # mem.write(startPosition = 0.i256, size = -1.i256, value = @[1.byte, 0.byte])
# #TODO deactivated because of no pow support in Stint: https://github.com/status-im/nim-stint/issues/37
# expect(ValidationError):
# var mem = memory32()
# mem.write(startPosition = 0.u256, size = pow(2.u256, 256), value = @[1.byte, 0.byte])
test "write rejects values beyond memory size":
expect(ValidationError):
var mem = memory128()
mem.write(startPos = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte])
var mem = memory128()
check mem.write(startPos = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte]).error.code == EvmErrorCode.MemoryFull
check mem.write(startPos = 128, value = 1.byte).error.code == EvmErrorCode.MemoryFull
test "extends appropriately extends memory":
var mem = newMemory()
var mem = EvmMemoryRef.new()
# Test extends to 32 byte array: 0 < (start_position + size) <= 32
mem.extend(startPos = 0, size = 10)
check(mem.bytes == repeat(0.byte, 32))
@ -64,7 +44,7 @@ proc memoryMain*() =
test "read returns correct bytes":
var mem = memory32()
mem.write(startPos = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check mem.write(startPos = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte]).isOk
check(mem.read(startPos = 5, size = 4) == @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPos = 6, size = 4) == @[0.byte, 1.byte, 0.byte, 0.byte])
check(mem.read(startPos = 1, size = 3) == @[0.byte, 0.byte, 0.byte])

View File

@ -67,10 +67,11 @@ proc overflowMain*() =
let privateKey = PrivateKey.fromHex("0000000000000000000000000000000000000000000000000000001000000000")[]
let tx = signTransaction(unsignedTx, privateKey, ChainId(1), false)
let res = testCallEvm(tx, tx.getSender, s, FkHomestead)
when defined(evmc_enabled):
check res.error == "EVMC_FAILURE"
else:
check res.error == "Opcode Dispatch Error: GasInt overflow, gasCost=2199123918888, gasRefund=9223372036845099570, depth=1"
check res.error == "Opcode Dispatch Error: GasIntOverflow, depth=1"
when isMainModule:
overflowMain()

View File

@ -14,7 +14,7 @@ import
vm_state,
vm_types,
constants,
vm_precompiles,
vm_precompiles {.all.},
transaction,
transaction/call_evm
],

View File

@ -1,26 +1,24 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-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.
import
std/importutils,
unittest2,
eth/common/eth_types,
../nimbus/[constants, errors, vm_internals]
../nimbus/evm/evm_errors,
../nimbus/evm/stack,
../nimbus/constants
template testPush(value: untyped, expected: untyped): untyped =
var stack = newStack()
stack.push(value)
privateAccess(EvmStackRef)
var stack = EvmStackRef.new()
check stack.push(value).isOk
check(stack.values == @[expected])
template testFailPush(value: untyped): untyped {.used.} =
var stack = newStack()
expect(ValidationError):
stack.push(value)
func toBytes(s: string): seq[byte] =
cast[seq[byte]](s)
@ -34,82 +32,68 @@ proc stackMain*() =
testPush(UINT_256_MAX, UINT_256_MAX)
testPush("ves".toBytes, "ves".toBytes.bigEndianToInt)
# Appveyor mysterious failure.
# Raising exception in this file will force the
# program to quit because of SIGSEGV.
# Cannot reproduce locally, and doesn't happen
# in other file.
when not(defined(windows) and
defined(cpu64) and
(NimMajor, NimMinor, NimPatch) >= (1, 0, 4)):
testFailPush("yzyzyzyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz".toBytes)
test "push does not allow stack to exceed 1024":
var stack = newStack()
var stack = EvmStackRef.new()
for z in 0 ..< 1024:
stack.push(z.uint)
check stack.push(z.uint).isOk
check(stack.len == 1024)
expect(FullStack):
stack.push(1025)
check stack.push(1025).error.code == EvmErrorCode.StackFull
test "dup does not allow stack to exceed 1024":
var stack = newStack()
stack.push(1.u256)
var stack = EvmStackRef.new()
check stack.push(1.u256).isOk
for z in 0 ..< 1023:
stack.dup(1)
check stack.dup(1).isOk
check(stack.len == 1024)
expect(FullStack):
stack.dup(1)
check stack.dup(1).error.code == EvmErrorCode.StackFull
test "pop returns latest stack item":
var stack = newStack()
var stack = EvmStackRef.new()
for element in @[1'u, 2'u, 3'u]:
stack.push(element)
check(stack.popInt == 3.u256)
check stack.push(element).isOk
check(stack.popInt.get == 3.u256)
test "swap correct":
var stack = newStack()
privateAccess(EvmStackRef)
var stack = EvmStackRef.new()
for z in 0 ..< 5:
stack.push(z.uint)
check stack.push(z.uint).isOk
check(stack.values == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256])
stack.swap(3)
check stack.swap(3).isOk
check(stack.values == @[0.u256, 4.u256, 2.u256, 3.u256, 1.u256])
stack.swap(1)
check stack.swap(1).isOk
check(stack.values == @[0.u256, 4.u256, 2.u256, 1.u256, 3.u256])
test "dup correct":
var stack = newStack()
privateAccess(EvmStackRef)
var stack = EvmStackRef.new()
for z in 0 ..< 5:
stack.push(z.uint)
check stack.push(z.uint).isOk
check(stack.values == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256])
stack.dup(1)
check stack.dup(1).isOk
check(stack.values == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256, 4.u256])
stack.dup(5)
check stack.dup(5).isOk
check(stack.values == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256, 4.u256, 1.u256])
test "pop raises InsufficientStack appropriately":
var stack = newStack()
expect(InsufficientStack):
discard stack.popInt()
var stack = EvmStackRef.new()
check stack.popInt().error.code == EvmErrorCode.StackInsufficient
test "swap raises InsufficientStack appropriately":
var stack = newStack()
expect(InsufficientStack):
stack.swap(0)
var stack = EvmStackRef.new()
check stack.swap(0).error.code == EvmErrorCode.StackInsufficient
test "dup raises InsufficientStack appropriately":
var stack = newStack()
expect(InsufficientStack):
stack.dup(0)
var stack = EvmStackRef.new()
check stack.dup(0).error.code == EvmErrorCode.StackInsufficient
test "binary operations raises InsufficientStack appropriately":
# https://github.com/status-im/nimbus/issues/31
# ./tests/fixtures/VMTests/vmArithmeticTest/mulUnderFlow.json
var stack = newStack()
stack.push(123)
expect(InsufficientStack):
discard stack.popInt(2)
var stack = EvmStackRef.new()
check stack.push(123).isOk
check stack.popInt(2).error.code == EvmErrorCode.StackInsufficient
when isMainModule:
stackMain()

View File

@ -64,7 +64,7 @@ proc toBytes(x: string): seq[byte] =
result = newSeq[byte](x.len)
for i in 0..<x.len: result[i] = x[i].byte
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 =
keccakHash(toBytes($blockNumber))
proc verifyResult(ctx: var StateContext, vmState: BaseVMState) =

View File

@ -1,2 +1,2 @@
{"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"Blake2b F function invalid input"}
{"output":"","gasUsed":"0x13498","error":"Blake2b F function invalid input"}
{"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"PrcInvalidParam"}
{"output":"","gasUsed":"0x13498","error":"PrcInvalidParam"}

View File

@ -6,7 +6,7 @@
{"pc":10,"op":96,"gas":"0x79bf79","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":12,"op":90,"gas":"0x79bf76","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xf3"],"depth":1,"refund":0,"opName":"GAS"}
{"pc":13,"op":241,"gas":"0x79bf74","gasCost":"0x77d89f","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xf3","0x79bf74"],"depth":1,"refund":0,"opName":"CALL"}
{"pc":0,"op":11,"gas":"0x77ce77","gasCost":"0x5","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"SIGNEXTEND","error":"Opcode Dispatch Error: Stack underflow, expect 2, got 0, depth=2"}
{"pc":0,"op":11,"gas":"0x77ce77","gasCost":"0x5","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"SIGNEXTEND","error":"Opcode Dispatch Error: StackInsufficient, depth=2"}
{"pc":14,"op":80,"gas":"0x1e6d5","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP"}
{"pc":15,"op":152,"gas":"0x1e6d3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"SWAP9","error":"Opcode Dispatch Error: Stack underflow for SWAP9, depth=1"}
{"output":"","gasUsed":"0x79bf88","error":"Opcode Dispatch Error: Stack underflow for SWAP9, depth=1"}
{"pc":15,"op":152,"gas":"0x1e6d3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"SWAP9","error":"Opcode Dispatch Error: StackInsufficient, depth=1"}
{"output":"","gasUsed":"0x79bf88","error":"Opcode Dispatch Error: StackInsufficient, depth=1"}

View File

@ -12,6 +12,6 @@
{"pc":8,"op":96,"gas":"0xf","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":10,"op":96,"gas":"0xc","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":12,"op":90,"gas":"0x9","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xfa"],"depth":2,"refund":0,"opName":"GAS"}
{"pc":13,"op":241,"gas":"0x7","gasCost":"0x9","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xfa","0x7"],"depth":2,"refund":0,"opName":"CALL","error":"Opcode Dispatch Error: Out of gas: Needed 9 - Remaining 7 - Reason: Call, depth=2"}
{"pc":12,"op":80,"gas":"0x0","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP","error":"Opcode Dispatch Error: Out of gas: Needed 2 - Remaining 0 - Reason: Pop, depth=1"}
{"output":"","gasUsed":"0xa54","error":"Opcode Dispatch Error: Out of gas: Needed 2 - Remaining 0 - Reason: Pop, depth=1"}
{"pc":13,"op":241,"gas":"0x7","gasCost":"0x9","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xfa","0x7"],"depth":2,"refund":0,"opName":"CALL","error":"Opcode Dispatch Error: OutOfGas, depth=2"}
{"pc":12,"op":80,"gas":"0x0","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP","error":"Opcode Dispatch Error: OutOfGas, depth=1"}
{"output":"","gasUsed":"0xa54","error":"Opcode Dispatch Error: OutOfGas, depth=1"}

View File

@ -334,19 +334,21 @@ proc setupAlloc(stateDB: LedgerRef, alloc: GenesisAlloc) =
for slot, value in acc.storage:
stateDB.setStorage(accAddr, slot, value)
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 =
# we can't raise exception here, it'll mess with EVM exception handler.
# so, store the exception for later using `hashError`
let num = blockNumber.truncate(uint64)
var h = Hash256()
if vmState.blockHashes.len == 0:
vmState.hashError = "getAncestorHash($1) invoked, no blockhashes provided" % [$num]
vmState.hashError = "getAncestorHash(" &
$num & ") invoked, no blockhashes provided"
return h
vmState.blockHashes.withValue(num, val) do:
h = val[]
do:
vmState.hashError = "getAncestorHash($1) invoked, blockhash for that block not provided" % [$num]
vmState.hashError = "getAncestorHash(" &
$num & ") invoked, blockhash for that block not provided"
return h