Remove exceptions from EVM (#2314)
* Remove exception from evm memory * Remove exception from gas meter * Remove exception from stack * Remove exception from precompiles * Remove exception from gas_costs * Remove exception from op handlers * Remove exception from op dispatcher * Remove exception from call_evm * Remove exception from EVM * Fix tools and tests * Remove exception from EVMC * fix evmc * Fix evmc * Remove remnants of async evm stuff * Remove superflous error handling * Proc to func * Fix errors detected by CI * Fix EVM op call stack usage * REmove exception handling from getVmState * Better error message instead of just doAssert * Remove unused validation * Remove superflous catchRaise * Use results.expect instead of unsafeValue
This commit is contained in:
parent
dd8095753a
commit
b3a5c67532
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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].} =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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)
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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* =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,7 +66,7 @@ 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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -13,14 +13,16 @@ import
|
|||
results,
|
||||
"."/[types, blake2b_f, blscurve],
|
||||
./interpreter/[gas_meter, gas_costs, utils/utils_numeric],
|
||||
../errors, eth/[common, keys], chronicles,
|
||||
nimcrypto/[ripemd, sha2, utils], bncurve/[fields, groups],
|
||||
eth/[common, keys],
|
||||
chronicles,
|
||||
nimcrypto/[ripemd, sha2, utils],
|
||||
bncurve/[fields, groups],
|
||||
../common/evmforks,
|
||||
../core/eip4844,
|
||||
./modexp,
|
||||
./evm_errors,
|
||||
./computation
|
||||
|
||||
|
||||
type
|
||||
PrecompileAddresses* = enum
|
||||
# Frontier to Spurious Dragron
|
||||
|
@ -50,8 +52,15 @@ type
|
|||
# paBlsMapG2
|
||||
# Cancun
|
||||
|
||||
SigRes = object
|
||||
msgHash: array[32, byte]
|
||||
sig: Signature
|
||||
|
||||
proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
|
||||
if fork < FkByzantium: paIdentity
|
||||
elif fork < FkIstanbul: paPairing
|
||||
# EIP 2537: disabled
|
||||
|
@ -60,60 +69,48 @@ proc getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
|
|||
elif fork < FkCancun: paBlake2bf
|
||||
else: PrecompileAddresses.high
|
||||
|
||||
proc validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool =
|
||||
func validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool =
|
||||
(addrByte in PrecompileAddresses.low.byte .. maxPrecompileAddr)
|
||||
|
||||
proc validPrecompileAddr(addrByte: byte, fork: EVMFork): bool =
|
||||
func validPrecompileAddr(addrByte: byte, fork: EVMFork): bool =
|
||||
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
|
||||
validPrecompileAddr(addrByte, maxPrecompileAddr.byte)
|
||||
|
||||
iterator activePrecompiles*(fork: EVMFork): EthAddress =
|
||||
var res: EthAddress
|
||||
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
|
||||
for c in PrecompileAddresses.low..maxPrecompileAddr:
|
||||
if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
|
||||
res[^1] = c.byte
|
||||
yield res
|
||||
|
||||
func activePrecompilesList*(fork: EVMFork): seq[EthAddress] =
|
||||
for address in activePrecompiles(fork):
|
||||
result.add address
|
||||
|
||||
proc getSignature(c: Computation): (array[32, byte], Signature) =
|
||||
func getSignature(c: Computation): EvmResult[SigRes] =
|
||||
# input is Hash, V, R, S
|
||||
template data: untyped = c.msg.data
|
||||
var bytes: array[65, byte] # will hold R[32], S[32], V[1], in that order
|
||||
let maxPos = min(data.high, 127)
|
||||
|
||||
# if we don't have at minimum 64 bytes, there can be no valid V
|
||||
if maxPos >= 63:
|
||||
let v = data[63]
|
||||
# check if V[32] is 27 or 28
|
||||
if not (v.int in 27..28):
|
||||
raise newException(ValidationError, "Invalid V in getSignature")
|
||||
for x in 32..<63:
|
||||
if data[x] != 0:
|
||||
raise newException(ValidationError, "Invalid V in getSignature")
|
||||
if maxPos < 63:
|
||||
return err(prcErr(PrcInvalidSig))
|
||||
|
||||
bytes[64] = v - 27
|
||||
let v = data[63]
|
||||
# check if V[32] is 27 or 28
|
||||
if not (v.int in 27..28):
|
||||
return err(prcErr(PrcInvalidSig))
|
||||
for x in 32..<63:
|
||||
if data[x] != 0:
|
||||
return err(prcErr(PrcInvalidSig))
|
||||
|
||||
# if there is more data for R and S, copy it. Else, defaulted zeroes are
|
||||
# used for R and S
|
||||
if maxPos >= 64:
|
||||
# Copy message data to buffer
|
||||
bytes[0..(maxPos-64)] = data[64..maxPos]
|
||||
bytes[64] = v - 27
|
||||
|
||||
let sig = Signature.fromRaw(bytes)
|
||||
if sig.isErr:
|
||||
raise newException(ValidationError, "Could not recover signature c")
|
||||
result[1] = sig[]
|
||||
# if there is more data for R and S, copy it. Else, defaulted zeroes are
|
||||
# used for R and S
|
||||
if maxPos >= 64:
|
||||
# Copy message data to buffer
|
||||
bytes[0..(maxPos-64)] = data[64..maxPos]
|
||||
|
||||
# extract message hash, only need to copy when there is a valid signature
|
||||
result[0][0..31] = data[0..31]
|
||||
else:
|
||||
raise newException(ValidationError, "Invalid V in getSignature")
|
||||
let sig = Signature.fromRaw(bytes).valueOr:
|
||||
return err(prcErr(PrcInvalidSig))
|
||||
var res = SigRes(sig: sig)
|
||||
|
||||
proc simpleDecode*(dst: var FQ2, src: openArray[byte]): bool {.noinit.} =
|
||||
# extract message hash, only need to copy when there is a valid signature
|
||||
res.msgHash[0..31] = data[0..31]
|
||||
ok(res)
|
||||
|
||||
func simpleDecode(dst: var FQ2, src: openArray[byte]): bool {.noinit.} =
|
||||
# bypassing FQ2.fromBytes
|
||||
# because we want to check `value > modulus`
|
||||
result = false
|
||||
|
@ -121,78 +118,85 @@ proc simpleDecode*(dst: var FQ2, src: openArray[byte]): bool {.noinit.} =
|
|||
dst.c0.fromBytes(src.toOpenArray(32, 63)):
|
||||
result = true
|
||||
|
||||
template simpleDecode*(dst: var FQ, src: openArray[byte]): bool =
|
||||
template simpleDecode(dst: var FQ, src: openArray[byte]): bool =
|
||||
fromBytes(dst, src)
|
||||
|
||||
proc getPoint[T: G1|G2](t: typedesc[T], data: openArray[byte]): Point[T] =
|
||||
func getPoint[T: G1|G2](_: typedesc[T], data: openArray[byte]): EvmResult[Point[T]] =
|
||||
when T is G1:
|
||||
const nextOffset = 32
|
||||
var px, py: FQ
|
||||
else:
|
||||
const nextOffset = 64
|
||||
var px, py: FQ2
|
||||
|
||||
if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)):
|
||||
raise newException(ValidationError, "Could not get point value")
|
||||
return err(prcErr(PrcInvalidPoint))
|
||||
if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)):
|
||||
raise newException(ValidationError, "Could not get point value")
|
||||
return err(prcErr(PrcInvalidPoint))
|
||||
|
||||
if px.isZero() and py.isZero():
|
||||
result = T.zero()
|
||||
ok(T.zero())
|
||||
else:
|
||||
var ap: AffinePoint[T]
|
||||
if not ap.init(px, py):
|
||||
raise newException(ValidationError, "Point is not on curve")
|
||||
result = ap.toJacobian()
|
||||
return err(prcErr(PrcInvalidPoint))
|
||||
ok(ap.toJacobian())
|
||||
|
||||
proc getFR(data: openArray[byte]): FR =
|
||||
if not result.fromBytes2(data):
|
||||
raise newException(ValidationError, "Could not get FR value")
|
||||
func getFR(data: openArray[byte]): EvmResult[FR] =
|
||||
var res: FR
|
||||
if not res.fromBytes2(data):
|
||||
return err(prcErr(PrcInvalidPoint))
|
||||
ok(res)
|
||||
|
||||
proc ecRecover*(c: Computation) =
|
||||
c.gasMeter.consumeGas(
|
||||
# ------------------------------------------------------------------------------
|
||||
# Precompiles functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func ecRecover(c: Computation): EvmResultVoid =
|
||||
? c.gasMeter.consumeGas(
|
||||
GasECRecover,
|
||||
reason="ECRecover Precompile")
|
||||
|
||||
var
|
||||
(msgHash, sig) = c.getSignature()
|
||||
|
||||
var pubkey = recover(sig, SkMessage(msgHash))
|
||||
if pubkey.isErr:
|
||||
raise newException(ValidationError, "Could not derive public key from c")
|
||||
let
|
||||
sig = ? c.getSignature()
|
||||
pubkey = recover(sig.sig, SkMessage(sig.msgHash)).valueOr:
|
||||
return err(prcErr(PrcInvalidSig))
|
||||
|
||||
c.output.setLen(32)
|
||||
c.output[12..31] = pubkey[].toCanonicalAddress()
|
||||
#trace "ECRecover precompile", derivedKey = pubkey[].toCanonicalAddress()
|
||||
c.output[12..31] = pubkey.toCanonicalAddress()
|
||||
ok()
|
||||
|
||||
proc sha256*(c: Computation) =
|
||||
func sha256(c: Computation): EvmResultVoid =
|
||||
let
|
||||
wordCount = wordCount(c.msg.data.len)
|
||||
gasFee = GasSHA256 + wordCount * GasSHA256Word
|
||||
|
||||
c.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile")
|
||||
c.output = @(sha2.sha256.digest(c.msg.data).data)
|
||||
#trace "SHA256 precompile", output = c.output.toHex
|
||||
ok()
|
||||
|
||||
proc ripemd160*(c: Computation) =
|
||||
func ripemd160(c: Computation): EvmResultVoid =
|
||||
let
|
||||
wordCount = wordCount(c.msg.data.len)
|
||||
gasFee = GasRIPEMD160 + wordCount * GasRIPEMD160Word
|
||||
|
||||
c.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile")
|
||||
c.output.setLen(32)
|
||||
c.output[12..31] = @(ripemd.ripemd160.digest(c.msg.data).data)
|
||||
#trace "RIPEMD160 precompile", output = c.output.toHex
|
||||
ok()
|
||||
|
||||
proc identity*(c: Computation) =
|
||||
func identity(c: Computation): EvmResultVoid =
|
||||
let
|
||||
wordCount = wordCount(c.msg.data.len)
|
||||
gasFee = GasIdentity + wordCount * GasIdentityWord
|
||||
|
||||
c.gasMeter.consumeGas(gasFee, reason="Identity Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason="Identity Precompile")
|
||||
c.output = c.msg.data
|
||||
#trace "Identity precompile", output = c.output.toHex
|
||||
ok()
|
||||
|
||||
proc modExpFee(c: Computation, baseLen, expLen, modLen: UInt256, fork: EVMFork): GasInt =
|
||||
func modExpFee(c: Computation,
|
||||
baseLen, expLen, modLen: UInt256,
|
||||
fork: EVMFork): EvmResult[GasInt] =
|
||||
template data: untyped {.dirty.} =
|
||||
c.msg.data
|
||||
|
||||
|
@ -237,15 +241,15 @@ proc modExpFee(c: Computation, baseLen, expLen, modLen: UInt256, fork: EVMFork):
|
|||
else: gasCalc(mulComplexity, GasQuadDivisor)
|
||||
|
||||
if gasFee > high(GasInt).u256:
|
||||
raise newException(OutOfGas, "modExp gas overflow")
|
||||
|
||||
result = gasFee.truncate(GasInt)
|
||||
return err(gasErr(OutOfGas))
|
||||
|
||||
var res = gasFee.truncate(GasInt)
|
||||
# EIP2565: modExp gas cost
|
||||
if fork >= FkBerlin and result < 200.GasInt:
|
||||
result = 200.GasInt
|
||||
if fork >= FkBerlin and res < 200.GasInt:
|
||||
res = 200.GasInt
|
||||
ok(res)
|
||||
|
||||
proc modExp*(c: Computation, fork: EVMFork = FkByzantium) =
|
||||
func modExp(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
|
||||
## Modular exponentiation precompiled contract
|
||||
## Yellow Paper Appendix E
|
||||
## EIP-198 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md
|
||||
|
@ -261,17 +265,17 @@ proc modExp*(c: Computation, fork: EVMFork = FkByzantium) =
|
|||
expLen = expL.safeInt
|
||||
modLen = modL.safeInt
|
||||
|
||||
let gasFee = modExpFee(c, baseL, expL, modL, fork)
|
||||
c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")
|
||||
let gasFee = ? modExpFee(c, baseL, expL, modL, fork)
|
||||
? c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")
|
||||
|
||||
if baseLen == 0 and modLen == 0:
|
||||
# This is a special case where expLength can be very big.
|
||||
c.output = @[]
|
||||
return
|
||||
return ok()
|
||||
|
||||
const maxSize = int32.high.u256
|
||||
if baseL > maxSize or expL > maxSize or modL > maxSize:
|
||||
raise newException(EVMError, "The Nimbus VM doesn't support oversized modExp operand")
|
||||
return err(prcErr(PrcInvalidParam))
|
||||
|
||||
# TODO:
|
||||
# add EVM special case:
|
||||
|
@ -291,10 +295,11 @@ proc modExp*(c: Computation, fork: EVMFork = FkByzantium) =
|
|||
else:
|
||||
c.output = newSeq[byte](modLen)
|
||||
c.output[^output.len..^1] = output[0..^1]
|
||||
ok()
|
||||
|
||||
proc bn256ecAdd*(c: Computation, fork: EVMFork = FkByzantium) =
|
||||
func bn256ecAdd(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
|
||||
let gasFee = if fork < FkIstanbul: GasECAdd else: GasECAddIstanbul
|
||||
c.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile")
|
||||
|
||||
var
|
||||
input: array[128, byte]
|
||||
|
@ -302,18 +307,19 @@ proc bn256ecAdd*(c: Computation, fork: EVMFork = FkByzantium) =
|
|||
# Padding data
|
||||
let len = min(c.msg.data.len, 128) - 1
|
||||
input[0..len] = c.msg.data[0..len]
|
||||
var p1 = G1.getPoint(input.toOpenArray(0, 63))
|
||||
var p2 = G1.getPoint(input.toOpenArray(64, 127))
|
||||
var p1 = ? G1.getPoint(input.toOpenArray(0, 63))
|
||||
var p2 = ? G1.getPoint(input.toOpenArray(64, 127))
|
||||
var apo = (p1 + p2).toAffine()
|
||||
if isSome(apo):
|
||||
# we can discard here because we supply proper buffer
|
||||
discard apo.get().toBytes(output)
|
||||
|
||||
c.output = @output
|
||||
ok()
|
||||
|
||||
proc bn256ecMul*(c: Computation, fork: EVMFork = FkByzantium) =
|
||||
func bn256ecMul(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
|
||||
let gasFee = if fork < FkIstanbul: GasECMul else: GasECMulIstanbul
|
||||
c.gasMeter.consumeGas(gasFee, reason="ecMul Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason="ecMul Precompile")
|
||||
|
||||
var
|
||||
input: array[96, byte]
|
||||
|
@ -322,26 +328,27 @@ proc bn256ecMul*(c: Computation, fork: EVMFork = FkByzantium) =
|
|||
# Padding data
|
||||
let len = min(c.msg.data.len, 96) - 1
|
||||
input[0..len] = c.msg.data[0..len]
|
||||
var p1 = G1.getPoint(input.toOpenArray(0, 63))
|
||||
var fr = getFR(input.toOpenArray(64, 95))
|
||||
var p1 = ? G1.getPoint(input.toOpenArray(0, 63))
|
||||
var fr = ? getFR(input.toOpenArray(64, 95))
|
||||
var apo = (p1 * fr).toAffine()
|
||||
if isSome(apo):
|
||||
# we can discard here because we supply buffer of proper size
|
||||
discard apo.get().toBytes(output)
|
||||
|
||||
c.output = @output
|
||||
ok()
|
||||
|
||||
proc bn256ecPairing*(c: Computation, fork: EVMFork = FkByzantium) =
|
||||
func bn256ecPairing(c: Computation, fork: EVMFork = FkByzantium): EvmResultVoid =
|
||||
let msglen = len(c.msg.data)
|
||||
if msglen mod 192 != 0:
|
||||
raise newException(ValidationError, "Invalid input length")
|
||||
return err(prcErr(PrcInvalidParam))
|
||||
|
||||
let numPoints = msglen div 192
|
||||
let gasFee = if fork < FkIstanbul:
|
||||
GasECPairingBase + numPoints * GasECPairingPerPoint
|
||||
else:
|
||||
GasECPairingBaseIstanbul + numPoints * GasECPairingPerPointIstanbul
|
||||
c.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile")
|
||||
|
||||
var output: array[32, byte]
|
||||
if msglen == 0:
|
||||
|
@ -356,9 +363,9 @@ proc bn256ecPairing*(c: Computation, fork: EVMFork = FkByzantium) =
|
|||
for i in 0..<count:
|
||||
let s = i * 192
|
||||
# Loading AffinePoint[G1], bytes from [0..63]
|
||||
var p1 = G1.getPoint(c.msg.data.toOpenArray(s, s + 63))
|
||||
var p1 = ? G1.getPoint(c.msg.data.toOpenArray(s, s + 63))
|
||||
# Loading AffinePoint[G2], bytes from [64..191]
|
||||
var p2 = G2.getPoint(c.msg.data.toOpenArray(s + 64, s + 191))
|
||||
var p2 = ? G2.getPoint(c.msg.data.toOpenArray(s + 64, s + 191))
|
||||
# Accumulate pairing result
|
||||
acc = acc * pairing(p1, p2)
|
||||
|
||||
|
@ -367,22 +374,25 @@ proc bn256ecPairing*(c: Computation, fork: EVMFork = FkByzantium) =
|
|||
discard BNU256.one().toBytes(output)
|
||||
|
||||
c.output = @output
|
||||
ok()
|
||||
|
||||
proc blake2bf*(c: Computation) =
|
||||
func blake2bf(c: Computation): EvmResultVoid =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
if len(input) == blake2FInputLength:
|
||||
let gasFee = GasInt(beLoad32(input, 0))
|
||||
c.gasMeter.consumeGas(gasFee, reason="blake2bf Precompile")
|
||||
? c.gasMeter.consumeGas(gasFee, reason="blake2bf Precompile")
|
||||
|
||||
var output: array[64, byte]
|
||||
if not blake2b_F(input, output):
|
||||
raise newException(ValidationError, "Blake2b F function invalid input")
|
||||
return err(prcErr(PrcInvalidParam))
|
||||
else:
|
||||
c.output = @output
|
||||
ok()
|
||||
|
||||
proc blsG1Add*(c: Computation) =
|
||||
#[
|
||||
func blsG1Add*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -404,7 +414,7 @@ proc blsG1Add*(c: Computation) =
|
|||
if not encodePoint(a, c.output):
|
||||
raise newException(ValidationError, "blsG1Add encodePoint error")
|
||||
|
||||
proc blsG1Mul*(c: Computation) =
|
||||
func blsG1Mul*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -458,7 +468,7 @@ func calcBlsMultiExpGas(K: int, gasCost: GasInt): GasInt =
|
|||
# Calculate gas and return the result
|
||||
result = (K * gasCost * discount) div 1000
|
||||
|
||||
proc blsG1MultiExp*(c: Computation) =
|
||||
func blsG1MultiExp*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -499,7 +509,7 @@ proc blsG1MultiExp*(c: Computation) =
|
|||
if not encodePoint(acc, c.output):
|
||||
raise newException(ValidationError, "blsG1MuliExp encodePoint error")
|
||||
|
||||
proc blsG2Add*(c: Computation) =
|
||||
func blsG2Add*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -521,7 +531,7 @@ proc blsG2Add*(c: Computation) =
|
|||
if not encodePoint(a, c.output):
|
||||
raise newException(ValidationError, "blsG2Add encodePoint error")
|
||||
|
||||
proc blsG2Mul*(c: Computation) =
|
||||
func blsG2Mul*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -544,7 +554,7 @@ proc blsG2Mul*(c: Computation) =
|
|||
if not encodePoint(a, c.output):
|
||||
raise newException(ValidationError, "blsG2Mul encodePoint error")
|
||||
|
||||
proc blsG2MultiExp*(c: Computation) =
|
||||
func blsG2MultiExp*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -585,7 +595,7 @@ proc blsG2MultiExp*(c: Computation) =
|
|||
if not encodePoint(acc, c.output):
|
||||
raise newException(ValidationError, "blsG2MuliExp encodePoint error")
|
||||
|
||||
proc blsPairing*(c: Computation) =
|
||||
func blsPairing*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -634,7 +644,7 @@ proc blsPairing*(c: Computation) =
|
|||
if acc.check():
|
||||
c.output[^1] = 1.byte
|
||||
|
||||
proc blsMapG1*(c: Computation) =
|
||||
func blsMapG1*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -653,7 +663,7 @@ proc blsMapG1*(c: Computation) =
|
|||
if not encodePoint(p, c.output):
|
||||
raise newException(ValidationError, "blsMapG1 encodePoint error")
|
||||
|
||||
proc blsMapG2*(c: Computation) =
|
||||
func blsMapG2*(c: Computation) =
|
||||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
|
@ -671,8 +681,9 @@ proc blsMapG2*(c: Computation) =
|
|||
c.output = newSeq[byte](256)
|
||||
if not encodePoint(p, c.output):
|
||||
raise newException(ValidationError, "blsMapG2 encodePoint error")
|
||||
]#
|
||||
|
||||
proc pointEvaluation*(c: Computation) =
|
||||
proc pointEvaluation(c: Computation): EvmResultVoid =
|
||||
# Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof.
|
||||
# Also verify that the provided commitment matches the provided versioned_hash.
|
||||
# The data is encoded as follows: versioned_hash | z | y | commitment | proof |
|
||||
|
@ -680,27 +691,43 @@ proc pointEvaluation*(c: Computation) =
|
|||
template input: untyped =
|
||||
c.msg.data
|
||||
|
||||
c.gasMeter.consumeGas(POINT_EVALUATION_PRECOMPILE_GAS,
|
||||
reason = "EIP-4844 Point Evaluation Precompile")
|
||||
? c.gasMeter.consumeGas(POINT_EVALUATION_PRECOMPILE_GAS,
|
||||
reason = "EIP-4844 Point Evaluation Precompile")
|
||||
|
||||
let res = pointEvaluation(input)
|
||||
if res.isErr:
|
||||
raise newException(ValidationError, res.error)
|
||||
pointEvaluation(input).isOkOr:
|
||||
return err(prcErr(PrcValidationError))
|
||||
|
||||
# return a constant
|
||||
c.output = @PointEvaluationResult
|
||||
ok()
|
||||
|
||||
proc execPrecompiles*(c: Computation, fork: EVMFork): bool {.inline.} =
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
iterator activePrecompiles*(fork: EVMFork): EthAddress =
|
||||
var res: EthAddress
|
||||
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
|
||||
for c in PrecompileAddresses.low..maxPrecompileAddr:
|
||||
if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
|
||||
res[^1] = c.byte
|
||||
yield res
|
||||
|
||||
func activePrecompilesList*(fork: EVMFork): seq[EthAddress] =
|
||||
for address in activePrecompiles(fork):
|
||||
result.add address
|
||||
|
||||
proc execPrecompiles*(c: Computation, fork: EVMFork): bool =
|
||||
for i in 0..18:
|
||||
if c.msg.codeAddress[i] != 0: return
|
||||
if c.msg.codeAddress[i] != 0:
|
||||
return false
|
||||
|
||||
let lb = c.msg.codeAddress[19]
|
||||
if not validPrecompileAddr(lb, fork):
|
||||
return
|
||||
return false
|
||||
|
||||
let precompile = PrecompileAddresses(lb)
|
||||
try:
|
||||
case precompile
|
||||
let res = case precompile
|
||||
of paEcRecover: ecRecover(c)
|
||||
of paSha256: sha256(c)
|
||||
of paRipeMd160: ripemd160(c)
|
||||
|
@ -723,12 +750,15 @@ proc execPrecompiles*(c: Computation, fork: EVMFork): bool {.inline.} =
|
|||
# of paBlsPairing: blsPairing(c)
|
||||
# of paBlsMapG1: blsMapG1(c)
|
||||
# of paBlsMapG2: blsMapG2(c)
|
||||
except OutOfGas as e:
|
||||
c.setError(EVMC_OUT_OF_GAS, e.msg, true)
|
||||
except CatchableError as e:
|
||||
if fork >= FkByzantium and precompile > paIdentity:
|
||||
c.setError(EVMC_PRECOMPILE_FAILURE, e.msg, true)
|
||||
|
||||
if res.isErr:
|
||||
if res.error.code == EvmErrorCode.OutOfGas:
|
||||
c.setError(EVMC_OUT_OF_GAS, $res.error.code, true)
|
||||
else:
|
||||
# swallow any other precompiles errors
|
||||
debug "execPrecompiles validation error", msg=e.msg
|
||||
if fork >= FkByzantium and precompile > paIdentity:
|
||||
c.setError(EVMC_PRECOMPILE_FAILURE, $res.error.code, true)
|
||||
else:
|
||||
# swallow any other precompiles errors
|
||||
debug "execPrecompiles validation error", errCode = $res.error.code
|
||||
|
||||
true
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}")
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -46,13 +46,13 @@ proc toBytes(x: string): seq[byte] =
|
|||
result = newSeq[byte](x.len)
|
||||
for i in 0..<x.len: result[i] = x[i].byte
|
||||
|
||||
method getAncestorHash*(vmState: BaseVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
|
||||
method getAncestorHash*(vmState: BaseVMState; blockNumber: BlockNumber): Hash256 =
|
||||
if blockNumber >= vmState.blockNumber:
|
||||
return
|
||||
return Hash256()
|
||||
elif blockNumber < 0:
|
||||
return
|
||||
return Hash256()
|
||||
elif blockNumber < vmState.blockNumber - 256:
|
||||
return
|
||||
return Hash256()
|
||||
else:
|
||||
return keccakHash(toBytes($blockNumber))
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -14,7 +14,7 @@ import
|
|||
vm_state,
|
||||
vm_types,
|
||||
constants,
|
||||
vm_precompiles,
|
||||
vm_precompiles {.all.},
|
||||
transaction,
|
||||
transaction/call_evm
|
||||
],
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -64,7 +64,7 @@ proc toBytes(x: string): seq[byte] =
|
|||
result = newSeq[byte](x.len)
|
||||
for i in 0..<x.len: result[i] = x[i].byte
|
||||
|
||||
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
|
||||
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 =
|
||||
keccakHash(toBytes($blockNumber))
|
||||
|
||||
proc verifyResult(ctx: var StateContext, vmState: BaseVMState) =
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"Blake2b F function invalid input"}
|
||||
{"output":"","gasUsed":"0x13498","error":"Blake2b F function invalid input"}
|
||||
{"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"PrcInvalidParam"}
|
||||
{"output":"","gasUsed":"0x13498","error":"PrcInvalidParam"}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{"pc":10,"op":96,"gas":"0x79bf79","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
|
||||
{"pc":12,"op":90,"gas":"0x79bf76","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xf3"],"depth":1,"refund":0,"opName":"GAS"}
|
||||
{"pc":13,"op":241,"gas":"0x79bf74","gasCost":"0x77d89f","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xf3","0x79bf74"],"depth":1,"refund":0,"opName":"CALL"}
|
||||
{"pc":0,"op":11,"gas":"0x77ce77","gasCost":"0x5","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"SIGNEXTEND","error":"Opcode Dispatch Error: Stack underflow, expect 2, got 0, depth=2"}
|
||||
{"pc":0,"op":11,"gas":"0x77ce77","gasCost":"0x5","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"SIGNEXTEND","error":"Opcode Dispatch Error: StackInsufficient, depth=2"}
|
||||
{"pc":14,"op":80,"gas":"0x1e6d5","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP"}
|
||||
{"pc":15,"op":152,"gas":"0x1e6d3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"SWAP9","error":"Opcode Dispatch Error: Stack underflow for SWAP9, depth=1"}
|
||||
{"output":"","gasUsed":"0x79bf88","error":"Opcode Dispatch Error: Stack underflow for SWAP9, depth=1"}
|
||||
{"pc":15,"op":152,"gas":"0x1e6d3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"SWAP9","error":"Opcode Dispatch Error: StackInsufficient, depth=1"}
|
||||
{"output":"","gasUsed":"0x79bf88","error":"Opcode Dispatch Error: StackInsufficient, depth=1"}
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
{"pc":8,"op":96,"gas":"0xf","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
|
||||
{"pc":10,"op":96,"gas":"0xc","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
|
||||
{"pc":12,"op":90,"gas":"0x9","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xfa"],"depth":2,"refund":0,"opName":"GAS"}
|
||||
{"pc":13,"op":241,"gas":"0x7","gasCost":"0x9","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xfa","0x7"],"depth":2,"refund":0,"opName":"CALL","error":"Opcode Dispatch Error: Out of gas: Needed 9 - Remaining 7 - Reason: Call, depth=2"}
|
||||
{"pc":12,"op":80,"gas":"0x0","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP","error":"Opcode Dispatch Error: Out of gas: Needed 2 - Remaining 0 - Reason: Pop, depth=1"}
|
||||
{"output":"","gasUsed":"0xa54","error":"Opcode Dispatch Error: Out of gas: Needed 2 - Remaining 0 - Reason: Pop, depth=1"}
|
||||
{"pc":13,"op":241,"gas":"0x7","gasCost":"0x9","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xfa","0x7"],"depth":2,"refund":0,"opName":"CALL","error":"Opcode Dispatch Error: OutOfGas, depth=2"}
|
||||
{"pc":12,"op":80,"gas":"0x0","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP","error":"Opcode Dispatch Error: OutOfGas, depth=1"}
|
||||
{"output":"","gasUsed":"0xa54","error":"Opcode Dispatch Error: OutOfGas, depth=1"}
|
||||
|
|
|
@ -334,19 +334,21 @@ proc setupAlloc(stateDB: LedgerRef, alloc: GenesisAlloc) =
|
|||
for slot, value in acc.storage:
|
||||
stateDB.setStorage(accAddr, slot, value)
|
||||
|
||||
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
|
||||
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 =
|
||||
# we can't raise exception here, it'll mess with EVM exception handler.
|
||||
# so, store the exception for later using `hashError`
|
||||
let num = blockNumber.truncate(uint64)
|
||||
var h = Hash256()
|
||||
if vmState.blockHashes.len == 0:
|
||||
vmState.hashError = "getAncestorHash($1) invoked, no blockhashes provided" % [$num]
|
||||
vmState.hashError = "getAncestorHash(" &
|
||||
$num & ") invoked, no blockhashes provided"
|
||||
return h
|
||||
|
||||
vmState.blockHashes.withValue(num, val) do:
|
||||
h = val[]
|
||||
do:
|
||||
vmState.hashError = "getAncestorHash($1) invoked, blockhash for that block not provided" % [$num]
|
||||
vmState.hashError = "getAncestorHash(" &
|
||||
$num & ") invoked, blockhash for that block not provided"
|
||||
|
||||
return h
|
||||
|
||||
|
|
Loading…
Reference in New Issue