From 3ba42ffcc9b4d8dcf18c12ac487af2e703483731 Mon Sep 17 00:00:00 2001 From: andri lim Date: Mon, 16 Dec 2019 14:12:07 +0700 Subject: [PATCH] implement Nim to C tests --- evmc/evmc.nim | 17 +++-- evmc/evmc_nim.nim | 62 +++++++++++++++-- tests/evmc_c/example_host.cpp | 10 ++- tests/test_host_vm.nim | 121 ++++++++++++++++++++++++++++++++-- 4 files changed, 193 insertions(+), 17 deletions(-) diff --git a/evmc/evmc.nim b/evmc/evmc.nim index c40f2d0..af27ad9 100644 --- a/evmc/evmc.nim +++ b/evmc/evmc.nim @@ -29,14 +29,14 @@ type # The fixed size array of 32 bytes. # 32 bytes of data capable of storing e.g. 256-bit hashes. evmc_bytes32* = object - bytes: array[32, byte] + bytes*: array[32, byte] # The alias for evmc_bytes32 to represent a big-endian 256-bit integer. evmc_uint256be* = evmc_bytes32 # Big-endian 160-bit hash suitable for keeping an Ethereum address. evmc_address* = object - bytes: array[20, byte] + bytes*: array[20, byte] # The kind of call-like instruction. evmc_call_kind* {.size: sizeof(cint).} = enum @@ -434,7 +434,7 @@ type # Destroys the VM instance. # # @param vm The VM instance to be destroyed. - evmc_destroy_fn* = proc(vm: var evmc_vm) {.cdecl.} + evmc_destroy_fn* = proc(vm: ptr evmc_vm) {.cdecl.} # Possible outcomes of evmc_set_option. evmc_set_option_result* {.size: sizeof(cint).} = enum @@ -453,7 +453,7 @@ type # @param name The option name. NULL-terminated string. Cannot be NULL. # @param value The new option value. NULL-terminated string. Cannot be NULL. # @return The outcome of the operation. - evmc_set_option_fn* = proc(vm: var evmc_vm, name, value: cstring): evmc_set_option_result {.cdecl.} + evmc_set_option_fn* = proc(vm: ptr evmc_vm, name, value: cstring): evmc_set_option_result {.cdecl.} # EVM revision. # @@ -513,7 +513,7 @@ type # @param code The reference to the code to be executed. This argument MAY be NULL. # @param code_size The length of the code. If @p code is NULL this argument MUST be 0. # @return The execution result. - evmc_execute_fn* = proc(vm: var evmc_vm, host: evmc_host_interface, + evmc_execute_fn* = proc(vm: ptr evmc_vm, host: ptr evmc_host_interface, context: evmc_host_context, rev: evmc_revision, msg: evmc_message, code: ptr byte, code_size: uint): evmc_result {.cdecl.} @@ -530,7 +530,7 @@ type # # @param vm The VM instance. # @return The supported capabilities of the VM. @see evmc_capabilities. - evmc_get_capabilities_fn* = proc(vm: evmc_vm): evmc_capabilities {.cdecl.} + evmc_get_capabilities_fn* = proc(vm: ptr evmc_vm): evmc_capabilities {.cdecl.} # The VM instance. # @@ -688,3 +688,8 @@ proc incl*(a: var evmc_capabilities, b: evmc_capabilities) {.inline.} = proc excl*(a: var evmc_capabilities, b: evmc_capabilities) {.inline.} = a = evmc_capabilities(a.uint32 and (not b.uint32)) + +proc contains*(a, b: evmc_capabilities): bool {.inline.} = + (a.uint32 and b.uint32) != 0 + +proc `==`*(a, b: evmc_status_code): bool {.borrow.} diff --git a/evmc/evmc_nim.nim b/evmc/evmc_nim.nim index 93bd161..f21a0f4 100644 --- a/evmc/evmc_nim.nim +++ b/evmc/evmc_nim.nim @@ -2,9 +2,20 @@ import evmc type HostContext* = object - host: evmc_host_interface + host: ptr evmc_host_interface context: evmc_host_context + EvmcVM* = object + vm: ptr evmc_vm + hc: HostContext + +proc init*(x: var HostContext, host: ptr evmc_host_interface, context: evmc_host_context) = + x.host = host + x.context = context + +proc init*(x: typedesc[HostContext], host: ptr evmc_host_interface, context: evmc_host_context): HostContext = + result.init(host, context) + proc getTxContext*(ctx: HostContext): evmc_tx_context = ctx.host.get_tx_context(ctx.context) @@ -24,14 +35,21 @@ proc setStorage*(ctx: HostContext, address: evmc_address, proc getBalance*(ctx: HostContext, address: evmc_address): evmc_uint256be = ctx.host.get_balance(ctx.context, address) -proc getCodeSize*(ctx: HostContext, address: evmc_address): uint = - ctx.host.get_code_size(ctx.context, address) +proc getCodeSize*(ctx: HostContext, address: evmc_address): int = + ctx.host.get_code_size(ctx.context, address).int proc getCodeHash*(ctx: HostContext, address: evmc_address): evmc_bytes32 = ctx.host.get_code_hash(ctx.context, address) -proc copyCode*(ctx: HostContext, address: evmc_address, code_offset: uint, buffer: openArray[byte]): uint = - ctx.host.copy_code(ctx.context, address, code_offset, buffer[0].unsafeAddr, buffer.len.uint) +proc copyCode*(ctx: HostContext, address: evmc_address, codeOffset: int = 0): seq[byte] = + let size = ctx.getCodeSize(address) + if size - codeOffset > 0: + result = newSeq[byte](size - codeOffset) + let read = ctx.host.copy_code(ctx.context, address, code_offset.uint, result[0].addr, result.len.uint).int + doAssert(read == result.len) + +proc copyCode*(ctx: HostContext, address: evmc_address, codeOffset: int, output: ptr byte, output_len: int): int = + ctx.host.copy_code(ctx.context, address, code_offset.uint, output, output_len.uint).int proc selfdestruct*(ctx: HostContext, address, beneficiary: evmc_address) = ctx.host.selfdestruct(ctx.context, address, beneficiary) @@ -41,3 +59,37 @@ proc emitLog*(ctx: HostContext, address: evmc_address, data: openArray[byte], to proc call*(ctx: HostContext, msg: evmc_message): evmc_result = ctx.host.call(ctx.context, msg) + +proc init*(x: var EvmcVM, vm: ptr evmc_vm, hc: HostContext) = + x.vm = vm + x.hc = hc + +proc init*(x: typedesc[EvmcVM], vm: ptr evmc_vm, hc: HostContext): EvmcVM = + result.init(vm, hc) + +proc init*(x: typedesc[EvmcVM], vm: ptr evmc_vm): EvmcVM = + result.init(vm, HostContext()) + +proc isABICompatible*(vm: EvmcVM): bool = + vm.vm.abi_version == EVMC_ABI_VERSION + +proc name*(vm: EvmcVM): string = + $vm.vm.name + +proc version*(vm: EvmcVM): string = + $vm.vm.version + +proc getCapabilities*(vm: EvmcVM): evmc_capabilities = + vm.vm.get_capabilities(vm.vm) + +proc setOption*(vm: EvmcVM, name, value: string): evmc_set_option_result = + if not vm.vm.set_option.isNil: + return vm.vm.set_option(vm.vm, name, value) + + result = EVMC_SET_OPTION_INVALID_NAME + +proc execute*(vm: EvmcVM, rev: evmc_revision, msg: evmc_message, code: openArray[byte]): evmc_result = + vm.vm.execute(vm.vm, vm.hc.host, vm.hc.context, rev, msg, code[0].unsafeAddr, code.len.uint) + +proc destroy*(vm: EvmcVM) = + vm.vm.destroy(vm.vm) diff --git a/tests/evmc_c/example_host.cpp b/tests/evmc_c/example_host.cpp index e3991fa..0c199b9 100644 --- a/tests/evmc_c/example_host.cpp +++ b/tests/evmc_c/example_host.cpp @@ -159,7 +159,6 @@ public: } }; - extern "C" { const evmc_host_interface* example_host_get_interface() @@ -169,7 +168,14 @@ const evmc_host_interface* example_host_get_interface() evmc_host_context* example_host_create_context(evmc_tx_context tx_context) { - auto host = new ExampleHost{tx_context}; + evmc::accounts accounts; + evmc::account acc; + evmc_address addr = {{0, 1, 2}}; + acc.balance = {{1, 0}}; + acc.code = {10, 11, 12, 13, 14, 15}; + accounts[addr] = acc; + + auto host = new ExampleHost{tx_context, accounts}; return host->to_context(); } diff --git a/tests/test_host_vm.nim b/tests/test_host_vm.nim index 6c9976f..3e67da4 100644 --- a/tests/test_host_vm.nim +++ b/tests/test_host_vm.nim @@ -1,17 +1,130 @@ -import ../evmc/[evmc, evmc_nim] +import ../evmc/[evmc, evmc_nim], unittest +import stew/byteutils {.compile: "evmc_c/example_host.cpp".} {.compile: "evmc_c/example_vm.c".} {.passL: "-lstdc++"} proc example_host_get_interface(): ptr evmc_host_interface {.importc, cdecl.} -proc example_host_create_context(tx_context: evmc_tx_context): ptr evmc_host_context {.importc, cdecl.} -proc example_host_destroy_context(context: ptr evmc_host_context) {.importc, cdecl.} +proc example_host_create_context(tx_context: evmc_tx_context): evmc_host_context {.importc, cdecl.} +proc example_host_destroy_context(context: evmc_host_context) {.importc, cdecl.} proc evmc_create_example_vm(): ptr evmc_vm {.importc, cdecl.} proc main() = - echo "tx_context: ", sizeof(evmc_tx_context) var vm = evmc_create_example_vm() var host = example_host_get_interface() + var code = hexToSeqByte("4360005543600052596000f3") + var input = "Hello World!" + const gas = 200000'i64 + var address: evmc_address + hexToByteArray("0x0001020000000000000000000000000000000000", address.bytes) + var balance: evmc_uint256be + hexToByteArray("0x0100000000000000000000000000000000000000000000000000000000000000", balance.bytes) + var ahash = evmc_bytes32(bytes: [0.byte, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + var tx_context = evmc_tx_context( + block_number: 42, + block_timestamp: 66, + block_gas_limit: gas * 2 + ) + + var msg = evmc_message( + kind: EVMC_CALL, + sender: address, + destination: address, + value: balance, + input_data: cast[ptr byte](input[0].addr), + input_size: input.len.uint, + gas: gas, + depth: 0 + ) + + var ctx = example_host_create_context(tx_context) + var hc = HostContext.init(host, ctx) + + suite "EVMC Nim to C API, host interface tests": + setup: + var + key: evmc_bytes32 + value: evmc_bytes32 + + hexToByteArray("0x0000000000000000000000000000000000000000000000000000000000000001", key.bytes) + hexToByteArray("0x0000000000000000000000000000000000000000000000000000000000000101", value.bytes) + + test "getTxContext": + let txc = hc.getTxContext() + check tx_context.block_number == txc.block_number + check tx_context.block_timestamp == txc.block_timestamp + check tx_context.block_gas_limit == txc.block_gas_limit + + test "getBlockHash": + var b10c: evmc_bytes32 + hexToByteArray("0xb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5f", + b10c.bytes) + let blockHash = hc.getBlockHash(tx_context.block_number - 1) + check blockHash == b10c + + test "setStorage": + check hc.setStorage(address, key, value) == EVMC_STORAGE_MODIFIED + + test "getStorage": + let val = hc.getStorage(address, key) + check val == value + + test "accountExists": + check hc.accountExists(address) == true + + test "getBalance": + let bal = hc.getBalance(address) + check bal == balance + + test "getCodeSize": + check hc.getCodeSize(address) == 6 + + test "getCodeHash": + let hash = hc.getCodeHash(address) + check hash == ahash + + test "copyCode": + let acode = @[11.byte, 12, 13, 14, 15] + let bcode = hc.copyCode(address, 1) + check acode == bcode + + test "selfdestruct": + hc.selfdestruct(address, address) + + test "emitlog": + hc.emitLog(address, code, [ahash]) + + test "call": + let res = hc.call(msg) + check res.status_code == EVMC_REVERT + check res.gas_left == msg.gas + check res.output_size == msg.input_size + check equalMem(res.output_data, msg.input_data, msg.input_size) + # no need to release the result, it's a fake one + + suite "EVMC Nim to C API, vm interface tests": + setup: + var nvm = EvmcVM.init(vm, hc) + + test "isABICompatible": + check nvm.isABICompatible() == true + + test "getCapabilities": + let cap = nvm.getCapabilities() + check EVMC_CAPABILITY_EVM1 in cap + check EVMC_CAPABILITY_EWASM in cap + + test "setOption": + check nvm.setOption("verbose", "2") == EVMC_SET_OPTION_SUCCESS + check nvm.setOption("debug", "true") == EVMC_SET_OPTION_INVALID_NAME + + test "execute and destroy": + var res = nvm.execute(EVMC_HOMESTEAD, msg, code) + check res.gas_left == 100000 + res.release(res) + nvm.destroy() + example_host_destroy_context(ctx) main()