157 lines
6.8 KiB
Nim
157 lines
6.8 KiB
Nim
# Nimbus - Binary compatibility on the host side of the EVMC API interface
|
|
#
|
|
# Copyright (c) 2019-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: [].}
|
|
|
|
when not declaredInScope(included_from_host_services):
|
|
{.error: "Do not import this file directly, import host_services instead".}
|
|
|
|
import evmc/evmc, ./evmc_dynamic_loader
|
|
|
|
template toHost(p: evmc_host_context): TransactionHost =
|
|
cast[TransactionHost](p)
|
|
|
|
proc accountExists(p: evmc_host_context, address: var evmc_address): c99bool {.cdecl.} =
|
|
toHost(p).accountExists(address.fromEvmc)
|
|
|
|
proc getStorage(p: evmc_host_context, address: var evmc_address,
|
|
key: var evmc_bytes32): evmc_bytes32
|
|
{.cdecl, raises: [].} =
|
|
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: [].} =
|
|
toHost(p).setStorage(address.fromEvmc, key.flip256.fromEvmc, value.flip256.fromEvmc)
|
|
|
|
proc getBalance(p: evmc_host_context,
|
|
address: var evmc_address): evmc_uint256be {.cdecl.} =
|
|
toHost(p).getBalance(address.fromEvmc).toEvmc.flip256
|
|
|
|
proc getCodeSize(p: evmc_host_context,
|
|
address: var evmc_address): csize_t {.cdecl.} =
|
|
toHost(p).getCodeSize(address.fromEvmc)
|
|
|
|
proc getCodeHash(p: evmc_host_context,
|
|
address: var evmc_address): evmc_bytes32 {.cdecl.} =
|
|
toHost(p).getCodeHash(address.fromEvmc).toEvmc
|
|
|
|
proc copyCode(p: evmc_host_context, address: var evmc_address, code_offset: csize_t,
|
|
buffer_data: ptr byte, buffer_size: csize_t): csize_t {.cdecl.} =
|
|
toHost(p).copyCode(address.fromEvmc, code_offset, buffer_data, buffer_size)
|
|
|
|
proc selfDestruct(p: evmc_host_context, address,
|
|
beneficiary: var evmc_address) {.cdecl.} =
|
|
toHost(p).selfDestruct(address.fromEvmc, beneficiary.fromEvmc)
|
|
|
|
proc call(p: evmc_host_context, msg: var evmc_message): evmc_result
|
|
{.cdecl, raises: [CatchableError].} =
|
|
# 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.
|
|
toHost(p).call(msg)
|
|
|
|
proc getTxContext(p: evmc_host_context): evmc_tx_context {.cdecl.} =
|
|
# This would contain `flip256`, but due to this result being cached in
|
|
# `getTxContext`, it's better to do `flip256` when filling the cache.
|
|
toHost(p).getTxContext()
|
|
|
|
proc getBlockHash(p: evmc_host_context, number: int64): evmc_bytes32
|
|
{.cdecl, raises: [CatchableError].} =
|
|
# TODO: `HostBlockNumber` is 256-bit unsigned. It should be changed to match
|
|
# EVMC which is more sensible.
|
|
toHost(p).getBlockHash(number.uint64.u256).toEvmc
|
|
|
|
proc emitLog(p: evmc_host_context, address: var evmc_address,
|
|
data: ptr byte, data_size: csize_t,
|
|
topics: ptr evmc_bytes32, topics_count: csize_t) {.cdecl.} =
|
|
toHost(p).emitLog(address.fromEvmc, data, data_size,
|
|
cast[ptr HostTopic](topics), topics_count)
|
|
|
|
proc accessAccount(p: evmc_host_context,
|
|
address: var evmc_address): evmc_access_status {.cdecl.} =
|
|
toHost(p).accessAccount(address.fromEvmc)
|
|
|
|
proc accessStorage(p: evmc_host_context, address: var evmc_address,
|
|
key: var evmc_bytes32): evmc_access_status {.cdecl.} =
|
|
toHost(p).accessStorage(address.fromEvmc, key.flip256.fromEvmc)
|
|
|
|
proc getTransientStorage(p: evmc_host_context, address: var evmc_address,
|
|
key: var evmc_bytes32): evmc_bytes32
|
|
{.cdecl, raises: [].} =
|
|
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: [].} =
|
|
toHost(p).setTransientStorage(address.fromEvmc, key.flip256.fromEvmc, value.flip256.fromEvmc)
|
|
|
|
let hostInterface = evmc_host_interface(
|
|
account_exists: accountExists,
|
|
get_storage: getStorage,
|
|
set_storage: setStorage,
|
|
get_balance: getBalance,
|
|
get_code_size: getCodeSize,
|
|
get_code_hash: getCodeHash,
|
|
copy_code: copyCode,
|
|
selfdestruct: selfDestruct,
|
|
call: call,
|
|
get_tx_context: getTxContext,
|
|
get_block_hash: getBlockHash,
|
|
emit_log: emitLog,
|
|
access_account: accessAccount,
|
|
access_storage: accessStorage,
|
|
get_transient_storage: getTransientStorage,
|
|
set_transient_storage: setTransientStorage,
|
|
)
|
|
|
|
proc evmcExecComputation*(host: TransactionHost): EvmcResult
|
|
{.raises: [CatchableError].} =
|
|
host.showCallEntry(host.msg)
|
|
|
|
let vm = evmcLoadVMCached()
|
|
if vm.isNil:
|
|
warn "No EVM"
|
|
# Nim defaults are fine for all other fields in the result object.
|
|
result = EvmcResult(status_code: EVMC_INTERNAL_ERROR)
|
|
host.showCallReturn(result)
|
|
return
|
|
|
|
let hostContext = cast[evmc_host_context](host)
|
|
host.hostInterface = hostInterface.unsafeAddr
|
|
|
|
# Without `{.gcsafe.}:` here, the call via `vm.execute` results in a Nim
|
|
# compile-time error in a far away function. Starting here, a cascade of
|
|
# warnings takes place: "Warning: '...' is not GC-safe as it performs an
|
|
# indirect call here [GCUnsafe2]", then a list of "Warning: '...' is not
|
|
# GC-safe as it calls '...'" at each function up the call stack, to a high
|
|
# level function `persistBlocks` where it terminates compilation as an error
|
|
# instead of a warning.
|
|
#
|
|
# It is tempting to annotate all EVMC API functions with `{.cdecl, gcsafe.}`,
|
|
# overriding the function signatures from the Nim EVMC module. Perhaps we
|
|
# will do that, though it's conceptually dubious, as the two sides of the
|
|
# EVMC ABI live in different GC worlds (when loaded as a shared library with
|
|
# its own Nim runtime), very similar to calling between threads.
|
|
#
|
|
# TODO: But wait: Why does the Nim EVMC test program compile fine without
|
|
# any `gcsafe`, even with `--threads:on`?
|
|
{.gcsafe.}:
|
|
result = vm.execute(vm, hostInterface.unsafeAddr, hostContext,
|
|
evmc_revision(host.vmState.fork.ord), host.msg,
|
|
if host.code.len > 0: host.code[0].unsafeAddr
|
|
else: nil,
|
|
host.code.len.csize_t)
|
|
|
|
host.showCallReturn(result)
|
|
|
|
# This code assumes fields, methods and types of ABI version 11, and must be
|
|
# checked for compatibility if the `import evmc/evmc` major version is updated.
|
|
when EVMC_ABI_VERSION != 11:
|
|
{.error: ("This code assumes EVMC_ABI_VERSION 11;" &
|
|
" update the code to use EVMC_ABI_VERSION " & $EVMC_ABI_VERSION).}
|