add mocked evmc Nim host

This commit is contained in:
andri lim 2019-12-16 16:00:41 +07:00
parent 3ba42ffcc9
commit 41e543cd94
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
2 changed files with 233 additions and 7 deletions

191
tests/evmc_nim/nim_host.nim Normal file
View File

@ -0,0 +1,191 @@
import tables, hashes, strutils
import ../../evmc/evmc
import stew/byteutils
type
Account = ref object
balance: evmc_uint256be
code: seq[byte]
storage: Table[evmc_bytes32, evmc_bytes32]
HostContext = ref object
tx_context: evmc_tx_context
accounts: Table[evmc_address, Account]
proc hash*(x: evmc_bytes32): Hash =
result = hash(x.bytes)
proc hash*(x: evmc_address): Hash =
result = hash(x.bytes)
proc codeHash(acc: Account): evmc_bytes32 =
# Extremely dumb "hash" function.
for v in acc.code:
let idx = v.int mod sizeof(result.bytes)
result.bytes[idx] = result.bytes[idx] xor v
proc evmcReleaseResultImpl(result: var evmc_result) {.cdecl.} =
discard
proc evmcGetTxContextImpl(ctx: HostContext): evmc_tx_context {.cdecl.} =
ctx.tx_context
proc evmcGetBlockHashImpl(ctx: HostContext, number: int64): evmc_bytes32 {.cdecl.} =
const hash = hexToByteArray[32]("0xb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5fb10c8a5f")
let current_block_number = ctx.tx_context.block_number
if number < current_block_number and number >= current_block_number - 256:
result.bytes = hash
proc evmcAccountExistsImpl(ctx: HostContext, address: evmc_address): c99bool {.cdecl.} =
address in ctx.accounts
proc evmcGetStorageImpl(ctx: HostContext, address: evmc_address, key: evmc_bytes32): evmc_bytes32 {.cdecl.} =
if address in ctx.accounts:
result = ctx.accounts[address].storage[key]
proc evmcSetStorageImpl(ctx: HostContext, address: evmc_address,
key, value: evmc_bytes32): evmc_storage_status {.cdecl.} =
if address in ctx.accounts:
var acc = ctx.accounts[address]
let prev_value = acc.storage.getOrDefault(key)
acc.storage[key] = value
result = if prev_value == value: EVMC_STORAGE_UNCHANGED else: EVMC_STORAGE_MODIFIED
else:
var acc = Account()
acc.storage[key] = value
ctx.accounts[address] = acc
result = EVMC_STORAGE_MODIFIED
proc evmcGetBalanceImpl(ctx: HostContext, address: evmc_address): evmc_uint256be {.cdecl.} =
if address in ctx.accounts:
result = ctx.accounts[address].balance
proc evmcGetCodeSizeImpl(ctx: HostContext, address: evmc_address): uint {.cdecl.} =
if address in ctx.accounts:
result = ctx.accounts[address].code.len.uint
proc evmcGetCodeHashImpl(ctx: HostContext, address: evmc_address): evmc_bytes32 {.cdecl.} =
if address in ctx.accounts:
result = ctx.accounts[address].codeHash()
proc evmcCopyCodeImpl(ctx: HostContext, address: evmc_address,
code_offset: uint, buffer_data: ptr byte,
buffer_size: uint): uint {.cdecl.} =
if address notin ctx.accounts:
return 0
let acc = ctx.accounts[address]
if code_offset.int >= acc.code.len:
return 0
let n = min(buffer_size.int, acc.code.len - code_offset.int)
if n > 0:
copyMem(buffer_data, acc.code[code_offset].addr, n)
result = n.uint
proc evmcSelfdestructImpl(ctx: HostContext, address, beneficiary: evmc_address) {.cdecl.} =
discard
proc evmcEmitLogImpl(ctx: HostContext, address: evmc_address,
data: ptr byte, data_size: uint,
topics: ptr evmc_bytes32, topics_count: uint) {.cdecl.} =
discard
proc evmcCallImpl(ctx: HostContext, msg: evmc_message): evmc_result {.cdecl.} =
result = evmc_result(status_code: EVMC_REVERT, gas_left: msg.gas, output_data: msg.input_data, output_size: msg.input_size)
proc evmcSetOptionImpl(vm: ptr evmc_vm, name, value: cstring): evmc_set_option_result {.cdecl.} =
let name = $name
if name == "verbose":
if value == nil:
return EVMC_SET_OPTION_INVALID_VALUE
try:
discard parseInt($value)
return EVMC_SET_OPTION_SUCCESS
except:
return EVMC_SET_OPTION_INVALID_VALUE
return EVMC_SET_OPTION_INVALID_NAME
proc evmcExecuteImpl(vm: ptr evmc_vm, host: ptr evmc_host_interface,
ctx: HostContext, rev: evmc_revision,
msg: evmc_message, code: ptr byte, code_size: uint): evmc_result {.cdecl.} =
var the_code = "\x43\x60\x00\x55\x43\x60\x00\x52\x59\x60\x00\xf3"
if (code_size.int == the_code.len) and equalMem(code, the_code[0].addr, code_size):
let tx_context = ctx.tx_context
let output_size = 20
var value, key: evmc_bytes32
value.bytes[31] = byte(tx_context.block_number)
discard ctx.evmcSetStorageImpl(msg.destination, key, value)
var output_data = alloc(output_size)
var bn = $tx_context.block_number
copyMem(output_data, bn[0].addr, bn.len)
result.status_code = EVMC_SUCCESS
result.gas_left = msg.gas div 2
result.output_data = cast[ptr byte](output_data)
result.output_size = output_size.uint
result.release = evmcReleaseResultImpl
return
result.status_code = EVMC_FAILURE
result.gas_left = 0
proc evmcGetCapabilitiesImpl(vm: ptr evmc_vm): evmc_capabilities {.cdecl.} =
result.incl(EVMC_CAPABILITY_EVM1)
result.incl(EVMC_CAPABILITY_EWASM)
proc evmcDestroyImpl(vm: ptr evmc_vm) {.cdecl.} =
dealloc(vm)
proc init_host_interface(): evmc_host_interface =
result.account_exists = cast[evmc_account_exists_fn](evmcAccountExistsImpl)
result.get_storage = cast[evmc_get_storage_fn](evmcGetStorageImpl)
result.set_storage = cast[evmc_set_storage_fn](evmcSetStorageImpl)
result.get_balance = cast[evmc_get_balance_fn](evmcGetBalanceImpl)
result.get_code_size = cast[evmc_get_code_size_fn](evmcGetCodeSizeImpl)
result.get_code_hash = cast[evmc_get_code_hash_fn](evmcGetCodeHashImpl)
result.copy_code = cast[evmc_copy_code_fn](evmcCopyCodeImpl)
result.selfdestruct = cast[evmc_selfdestruct_fn](evmcSelfdestructImpl)
result.call = cast[evmc_call_fn](evmcCallImpl)
result.get_tx_context = cast[evmc_get_tx_context_fn](evmcGetTxContextImpl)
result.get_block_hash = cast[evmc_get_block_hash_fn](evmcGetBlockHashImpl)
result.emit_log = cast[evmc_emit_log_fn](evmcEmitLogImpl)
const
EVMC_HOST_NAME = "example_vm"
EVMC_VM_VERSION = "0.0.0"
proc init(vm: var evmc_vm) {.exportc, cdecl.} =
vm.abi_version = EVMC_ABI_VERSION
vm.name = EVMC_HOST_NAME
vm.version = EVMC_VM_VERSION
vm.destroy = evmcDestroyImpl
vm.execute = cast[evmc_execute_fn](evmcExecuteImpl)
vm.get_capabilities = evmcGetCapabilitiesImpl
vm.set_option = evmcSetOptionImpl
let gHost = init_host_interface()
proc nim_host_get_interface(): ptr evmc_host_interface {.exportc, cdecl.} =
result = gHost.unsafeAddr
proc nim_host_create_context(tx_context: evmc_tx_context): HostContext {.exportc, cdecl.} =
const address = evmc_address(bytes: hexToByteArray[20]("0x0001020000000000000000000000000000000000"))
var acc = Account(
balance: evmc_uint256be(bytes: hexToByteArray[32]("0x0100000000000000000000000000000000000000000000000000000000000000")),
code: @[10.byte, 11, 12, 13, 14, 15]
)
result = HostContext(tx_context: tx_context)
result.accounts[address] = acc
GC_ref(result)
proc nim_host_destroy_context(ctx: HostContext) {.exportc, cdecl.} =
GC_unref(ctx)
proc nim_create_example_vm(): ptr evmc_vm {.exportc, cdecl.} =
result = create(evmc_vm)
init(result[])

View File

@ -1,4 +1,5 @@
import ../evmc/[evmc, evmc_nim], unittest
import evmc_nim/nim_host
import stew/byteutils
{.compile: "evmc_c/example_host.cpp".}
@ -10,9 +11,14 @@ proc example_host_create_context(tx_context: evmc_tx_context): evmc_host_context
proc example_host_destroy_context(context: evmc_host_context) {.importc, cdecl.}
proc evmc_create_example_vm(): ptr evmc_vm {.importc, cdecl.}
proc main() =
var vm = evmc_create_example_vm()
var host = example_host_get_interface()
proc nim_host_get_interface(): ptr evmc_host_interface {.importc, cdecl.}
proc nim_host_create_context(tx_context: evmc_tx_context): evmc_host_context {.importc, cdecl.}
proc nim_host_destroy_context(context: evmc_host_context) {.importc, cdecl.}
proc nim_create_example_vm(): ptr evmc_vm {.importc, cdecl.}
template runTest(testName: string, create_vm, get_host_interface, create_host_context, destroy_host_context: untyped) =
var vm = create_vm()
var host = get_host_interface()
var code = hexToSeqByte("4360005543600052596000f3")
var input = "Hello World!"
const gas = 200000'i64
@ -39,10 +45,10 @@ proc main() =
depth: 0
)
var ctx = example_host_create_context(tx_context)
var ctx = create_host_context(tx_context)
var hc = HostContext.init(host, ctx)
suite "EVMC Nim to C API, host interface tests":
suite testName & ", host interface tests":
setup:
var
key: evmc_bytes32
@ -104,13 +110,19 @@ proc main() =
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":
suite testName & ", vm interface tests":
setup:
var nvm = EvmcVM.init(vm, hc)
test "isABICompatible":
check nvm.isABICompatible() == true
test "vm.name":
check nvm.name() == "example_vm"
test "vm.version":
check nvm.version() == "0.0.0"
test "getCapabilities":
let cap = nvm.getCapabilities()
check EVMC_CAPABILITY_EVM1 in cap
@ -121,10 +133,33 @@ proc main() =
check nvm.setOption("debug", "true") == EVMC_SET_OPTION_INVALID_NAME
test "execute and destroy":
var bn = $tx_context.block_number
var res = nvm.execute(EVMC_HOMESTEAD, msg, code)
check res.status_code == EVMC_SUCCESS
check res.gas_left == 100000
check equalMem(bn[0].addr, res.output_data, bn.len)
res.release(res)
var empty_key: evmc_bytes32
let val = hc.getStorage(address, empty_key)
check val.bytes[31] == tx_context.block_number.byte
nvm.destroy()
example_host_destroy_context(ctx)
destroy_host_context(ctx)
proc main() =
runTest("EVMC Nim to C API",
evmc_create_example_vm,
example_host_get_interface,
example_host_create_context,
example_host_destroy_context
)
runTest("EVMC Nim to Nim API",
nim_create_example_vm,
nim_host_get_interface,
nim_host_create_context,
nim_host_destroy_context
)
main()