diff --git a/nimbus/core/chain/persist_blocks.nim b/nimbus/core/chain/persist_blocks.nim index 7ed7b6123..14522c483 100644 --- a/nimbus/core/chain/persist_blocks.nim +++ b/nimbus/core/chain/persist_blocks.nim @@ -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() diff --git a/nimbus/core/executor/process_block.nim b/nimbus/core/executor/process_block.nim index dc2c1ab18..bde378890 100644 --- a/nimbus/core/executor/process_block.nim +++ b/nimbus/core/executor/process_block.nim @@ -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 diff --git a/nimbus/core/executor/process_transaction.nim b/nimbus/core/executor/process_transaction.nim index f431fccd1..4172f2f18 100644 --- a/nimbus/core/executor/process_transaction.nim +++ b/nimbus/core/executor/process_transaction.nim @@ -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) diff --git a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim index 0df6f1108..d0d4dd019 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim @@ -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].} = diff --git a/nimbus/evm/computation.nim b/nimbus/evm/computation.nim index 057e391d6..3a637d0c8 100644 --- a/nimbus/evm/computation.nim +++ b/nimbus/evm/computation.nim @@ -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, diff --git a/nimbus/evm/evm_errors.nim b/nimbus/evm/evm_errors.nim new file mode 100644 index 000000000..fb32f286d --- /dev/null +++ b/nimbus/evm/evm_errors.nim @@ -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, + ) diff --git a/nimbus/evm/evmc_api.nim b/nimbus/evm/evmc_api.nim index 366a38678..172726b6a 100644 --- a/nimbus/evm/evmc_api.nim +++ b/nimbus/evm/evmc_api.nim @@ -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) diff --git a/nimbus/evm/interpreter/gas_costs.nim b/nimbus/evm/interpreter/gas_costs.nim index 350fb8bac..ef820d879 100644 --- a/nimbus/evm/interpreter/gas_costs.nim +++ b/nimbus/evm/interpreter/gas_costs.nim @@ -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`, diff --git a/nimbus/evm/interpreter/gas_meter.nim b/nimbus/evm/interpreter/gas_meter.nim index f065bbc21..efef40956 100644 --- a/nimbus/evm/interpreter/gas_meter.nim +++ b/nimbus/evm/interpreter/gas_meter.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_dispatcher.nim b/nimbus/evm/interpreter/op_dispatcher.nim index b84bba784..110b84e33 100644 --- a/nimbus/evm/interpreter/op_dispatcher.nim +++ b/nimbus/evm/interpreter/op_dispatcher.nim @@ -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) diff --git a/nimbus/evm/interpreter/op_handlers/oph_arithmetic.nim b/nimbus/evm/interpreter/op_handlers/oph_arithmetic.nim index 1f5091141..4a73145a6 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_arithmetic.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_arithmetic.nim @@ -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 two’s 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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim b/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim index 29d287ae1..313b81270 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_blockdata.nim @@ -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 chain’s 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 # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/interpreter/op_handlers/oph_call.nim b/nimbus/evm/interpreter/op_handlers/oph_call.nim index 41c69a8af..d4083e6a7 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_call.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_call.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_create.nim b/nimbus/evm/interpreter/op_handlers/oph_create.nim index 83d46cb40..8bcb7b421 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_create.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_create.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_defs.nim b/nimbus/evm/interpreter/op_handlers/oph_defs.nim index f355f173e..49e59a8a5 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_defs.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_defs.nim @@ -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* = diff --git a/nimbus/evm/interpreter/op_handlers/oph_dup.nim b/nimbus/evm/interpreter/op_handlers/oph_dup.nim index 0446a2681..7a386997d 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_dup.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_dup.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim b/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim index 1f3b5d018..e1a9c7cab 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim @@ -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 contract’s 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 contract’s 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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_gen_handlers.nim b/nimbus/evm/interpreter/op_handlers/oph_gen_handlers.nim index ab334eda2..216cbc5b7 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_gen_handlers.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_gen_handlers.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_hash.nim b/nimbus/evm/interpreter/op_handlers/oph_hash.nim index ad7c0b221..db5535f41 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_hash.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_hash.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_helpers.nim b/nimbus/evm/interpreter/op_handlers/oph_helpers.nim index 57ea048f6..af9a07e6e 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_helpers.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_helpers.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_log.nim b/nimbus/evm/interpreter/op_handlers/oph_log.nim index 1fa83e41f..56a160420 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_log.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_log.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_memory.nim b/nimbus/evm/interpreter/op_handlers/oph_memory.nim index 0bc901159..cb8f8321c 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_memory.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_memory.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_push.nim b/nimbus/evm/interpreter/op_handlers/oph_push.nim index 25a632270..56d5cb978 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_push.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_push.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_swap.nim b/nimbus/evm/interpreter/op_handlers/oph_swap.nim index d5614b9cc..ee385196b 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_swap.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_swap.nim @@ -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 diff --git a/nimbus/evm/interpreter/op_handlers/oph_sysops.nim b/nimbus/evm/interpreter/op_handlers/oph_sysops.nim index 03a233e27..9ca15011b 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_sysops.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_sysops.nim @@ -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 diff --git a/nimbus/evm/interpreter_dispatch.nim b/nimbus/evm/interpreter_dispatch.nim index ebb0e5738..c3c6b8a25 100644 --- a/nimbus/evm/interpreter_dispatch.nim +++ b/nimbus/evm/interpreter_dispatch.nim @@ -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 # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/memory.nim b/nimbus/evm/memory.nim index a86d94f75..4a7f7fef9 100644 --- a/nimbus/evm/memory.nim +++ b/nimbus/evm/memory.nim @@ -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) diff --git a/nimbus/evm/precompiles.nim b/nimbus/evm/precompiles.nim index c495c6125..a03ade4a8 100644 --- a/nimbus/evm/precompiles.nim +++ b/nimbus/evm/precompiles.nim @@ -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..= 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 diff --git a/nimbus/evm/stack.nim b/nimbus/evm/stack.nim index b8004faab..22fcf41fc 100644 --- a/nimbus/evm/stack.nim +++ b/nimbus/evm/stack.nim @@ -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) diff --git a/nimbus/evm/state.nim b/nimbus/evm/state.nim index 03f767578..41f89ae36 100644 --- a/nimbus/evm/state.nim +++ b/nimbus/evm/state.nim @@ -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) diff --git a/nimbus/evm/state_transactions.nim b/nimbus/evm/state_transactions.nim index b53b42a26..b1658beaf 100644 --- a/nimbus/evm/state_transactions.nim +++ b/nimbus/evm/state_transactions.nim @@ -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() diff --git a/nimbus/evm/tracer/access_list_tracer.nim b/nimbus/evm/tracer/access_list_tracer.nim index b515300bc..d7fa0e55d 100644 --- a/nimbus/evm/tracer/access_list_tracer.nim +++ b/nimbus/evm/tracer/access_list_tracer.nim @@ -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 diff --git a/nimbus/evm/tracer/json_tracer.nim b/nimbus/evm/tracer/json_tracer.nim index 398a97a61..bed96c832 100644 --- a/nimbus/evm/tracer/json_tracer.nim +++ b/nimbus/evm/tracer/json_tracer.nim @@ -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 diff --git a/nimbus/evm/tracer/legacy_tracer.nim b/nimbus/evm/tracer/legacy_tracer.nim index f48adf6dc..9a2fdbc1a 100644 --- a/nimbus/evm/tracer/legacy_tracer.nim +++ b/nimbus/evm/tracer/legacy_tracer.nim @@ -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 diff --git a/nimbus/evm/types.nim b/nimbus/evm/types.nim index 5e3cbfef5..8abe8ab8e 100644 --- a/nimbus/evm/types.nim +++ b/nimbus/evm/types.nim @@ -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 diff --git a/nimbus/evm/validation.nim b/nimbus/evm/validation.nim deleted file mode 100644 index eaf8343ed..000000000 --- a/nimbus/evm/validation.nim +++ /dev/null @@ -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}") diff --git a/nimbus/graphql/ethapi.nim b/nimbus/graphql/ethapi.nim index b643870ba..6fd02ff44 100644 --- a/nimbus/graphql/ethapi.nim +++ b/nimbus/graphql/ethapi.nim @@ -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) diff --git a/nimbus/rpc/experimental.nim b/nimbus/rpc/experimental.nim index 0ad48ff41..a5d846e4b 100644 --- a/nimbus/rpc/experimental.nim +++ b/nimbus/rpc/experimental.nim @@ -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() diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index fc4fb68a5..e3bdf186e 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -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: diff --git a/nimbus/rpc/params.nim b/nimbus/rpc/params.nim index 8603854bd..a78b8d398 100644 --- a/nimbus/rpc/params.nim +++ b/nimbus/rpc/params.nim @@ -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.} diff --git a/nimbus/rpc/rpc_utils.nim b/nimbus/rpc/rpc_utils.nim index bf16f1ae3..3e10de849 100644 --- a/nimbus/rpc/rpc_utils.nim +++ b/nimbus/rpc/rpc_utils.nim @@ -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( diff --git a/nimbus/tracer.nim b/nimbus/tracer.nim index 1adac782f..f56c1ada2 100644 --- a/nimbus/tracer.nim +++ b/nimbus/tracer.nim @@ -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() diff --git a/nimbus/transaction/call_common.nim b/nimbus/transaction/call_common.nim index 74fd169bf..7208a5db2 100644 --- a/nimbus/transaction/call_common.nim +++ b/nimbus/transaction/call_common.nim @@ -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) diff --git a/nimbus/transaction/call_evm.nim b/nimbus/transaction/call_evm.nim index b569a4c1e..6c68eb50b 100644 --- a/nimbus/transaction/call_evm.nim +++ b/nimbus/transaction/call_evm.nim @@ -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) diff --git a/nimbus/transaction/evmc_dynamic_loader.nim b/nimbus/transaction/evmc_dynamic_loader.nim index 13a45ca86..c475f3a33 100644 --- a/nimbus/transaction/evmc_dynamic_loader.nim +++ b/nimbus/transaction/evmc_dynamic_loader.nim @@ -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 diff --git a/nimbus/transaction/evmc_host_glue.nim b/nimbus/transaction/evmc_host_glue.nim index 39b1f6309..54f6ae2b0 100644 --- a/nimbus/transaction/evmc_host_glue.nim +++ b/nimbus/transaction/evmc_host_glue.nim @@ -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() diff --git a/nimbus/transaction/evmc_vm_glue.nim b/nimbus/transaction/evmc_vm_glue.nim index 482d6c3fe..5cd443285 100644 --- a/nimbus/transaction/evmc_vm_glue.nim +++ b/nimbus/transaction/evmc_vm_glue.nim @@ -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 diff --git a/nimbus/transaction/host_call_nested.nim b/nimbus/transaction/host_call_nested.nim index cf4427c8c..d3a893f2a 100644 --- a/nimbus/transaction/host_call_nested.nim +++ b/nimbus/transaction/host_call_nested.nim @@ -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) diff --git a/nimbus/transaction/host_trace.nim b/nimbus/transaction/host_trace.nim index b9a0756c4..a090f6155 100644 --- a/nimbus/transaction/host_trace.nim +++ b/nimbus/transaction/host_trace.nim @@ -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]() diff --git a/nimbus/vm_internals.nim b/nimbus/vm_internals.nim index 7295a565e..cf4b54ba2 100644 --- a/nimbus/vm_internals.nim +++ b/nimbus/vm_internals.nim @@ -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 diff --git a/nimbus/vm_precompiles.nim b/nimbus/vm_precompiles.nim index 1e296110b..cc3b7ec92 100644 --- a/nimbus/vm_precompiles.nim +++ b/nimbus/vm_precompiles.nim @@ -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 diff --git a/premix/no-hunter.nim b/premix/no-hunter.nim index 3b8ee38bc..fa54b6c57 100644 --- a/premix/no-hunter.nim +++ b/premix/no-hunter.nim @@ -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: diff --git a/tests/macro_assembler.nim b/tests/macro_assembler.nim index b89f99e49..1ffd76cc7 100644 --- a/tests/macro_assembler.nim +++ b/tests/macro_assembler.nim @@ -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: diff --git a/tests/test_gas_meter.nim b/tests/test_gas_meter.nim index c91055203..52e85ebb0 100644 --- a/tests/test_gas_meter.nim +++ b/tests/test_gas_meter.nim @@ -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) diff --git a/tests/test_generalstate_json.nim b/tests/test_generalstate_json.nim index e1b8b130d..abbefd4c1 100644 --- a/tests/test_generalstate_json.nim +++ b/tests/test_generalstate_json.nim @@ -46,13 +46,13 @@ proc toBytes(x: string): seq[byte] = result = newSeq[byte](x.len) for i in 0..= vmState.blockNumber: - return + return Hash256() elif blockNumber < 0: - return + return Hash256() elif blockNumber < vmState.blockNumber - 256: - return + return Hash256() else: return keccakHash(toBytes($blockNumber)) diff --git a/tests/test_memory.nim b/tests/test_memory.nim index b13f00cf1..829dd8b3c 100644 --- a/tests/test_memory.nim +++ b/tests/test_memory.nim @@ -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]) diff --git a/tests/test_overflow.nim b/tests/test_overflow.nim index a4c8e0dac..1fa3ff979 100644 --- a/tests/test_overflow.nim +++ b/tests/test_overflow.nim @@ -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() diff --git a/tests/test_precompiles.nim b/tests/test_precompiles.nim index 60b68c453..2ae8e716e 100644 --- a/tests/test_precompiles.nim +++ b/tests/test_precompiles.nim @@ -14,7 +14,7 @@ import vm_state, vm_types, constants, - vm_precompiles, + vm_precompiles {.all.}, transaction, transaction/call_evm ], diff --git a/tests/test_stack.nim b/tests/test_stack.nim index c046745bf..596367bfb 100644 --- a/tests/test_stack.nim +++ b/tests/test_stack.nim @@ -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() diff --git a/tools/evmstate/evmstate.nim b/tools/evmstate/evmstate.nim index ad7dfb4ad..56eb4a24d 100644 --- a/tools/evmstate/evmstate.nim +++ b/tools/evmstate/evmstate.nim @@ -64,7 +64,7 @@ proc toBytes(x: string): seq[byte] = result = newSeq[byte](x.len) for i in 0..