Add C-API example

This commit is contained in:
mratsim 2018-02-07 15:14:44 +01:00
parent b3bd9d5e01
commit 9c384a5e9a
2 changed files with 262 additions and 0 deletions

155
examples/capi.nim Normal file
View File

@ -0,0 +1,155 @@
import ../src/evmjit, examplevm, strutils
proc balance(context: ptr evm_context,
address: ptr evm_address): evm_uint256be =
result.bytes[0..3] = [1.uint8, 2, 3, 4]
proc address(context: ptr evm_context): evm_address =
result.bytes[0..3] = [1.uint8, 2, 3, 4]
proc `$`(address: evm_address):string =
result = ""
for i in address.bytes:
result.add($i)
proc account_exists(context: ptr evm_context, address: ptr evm_address): cint {.cdecl.}=
echo "EVM-C: EXISTS @"
echo address[]
echo "\n"
return 0
proc get_storage(result: ptr evm_uint256be,
context: ptr evm_context,
address: ptr evm_address,
key: ptr evm_uint256be) {.cdecl.}=
echo "EVM-C: SLOAD @"
echo address[]
echo "\n"
proc set_storage(context: ptr evm_context,
address: ptr evm_address,
key: ptr evm_uint256be,
value: ptr evm_uint256be) {.cdecl.}=
echo "EVM-C: SSTORE @"
echo address[]
echo "\n"
proc get_balance(result: ptr evm_uint256be,
context: ptr evm_context,
address: ptr evm_address) {.cdecl.}=
echo "EVM-C: BALANCE @"
echo address[]
echo "\n"
result[] = balance(context, address)
proc get_code(code: ptr ptr uint8,
context: ptr evm_context,
address: ptr evm_address): csize {.cdecl.}=
echo "EVM-C: CODE @"
echo address[]
echo "\n"
return 0
proc selfdestruct(context: ptr evm_context,
address: ptr evm_address,
beneficiary: ptr evm_address) {.cdecl.}=
echo "EVM-C: SELFDESTRUCT"
echo address[]
echo " -> "
echo beneficiary[]
echo "\n"
proc call(result: ptr evm_result,
context: ptr evm_context,
msg: ptr evm_message) {.cdecl.}=
echo "EVM-C: CALL (depth: %1)\n" % $msg.depth
result.status_code = EVM_FAILURE
proc get_tx_context(result: ptr evm_tx_context, context: ptr evm_context) {.cdecl.}=
discard
proc get_block_hash(result: ptr evm_uint256be, context: ptr evm_context, number: int64) {.cdecl.}=
discard
# EVM log callback
# Note: the evm_log name is used to avoid conflict with `log()`C function.
proc evm_log(context: ptr evm_context,
address: ptr evm_address,
data: ptr uint8,
data_size: csize,
topics: ptr evm_uint256be,
topics_count: csize) {.cdecl.}=
echo "EVM-C: LOG%1\n" % $topics_count
const ctx_fn_table = evm_context_fn_table(
account_exists: account_exists,
get_storage: get_storage,
set_storage: set_storage,
get_balance: get_balance,
get_code: get_code,
selfdestruct: selfdestruct,
call: call,
get_tx_context: get_tx_context,
get_block_hash: get_block_hash,
emit_log: evm_log
)
# Example of how the API is supposed to be used
proc main() =
let jit = examplevm_create()
if jit.abi_version != EVM_ABI_VERSION:
raise newException(LibraryError, "Incompatible ABI version")
let
# code: cstring = "Place some EVM bytecode here"
code: cstring = "600160005401600055"
code_size = code.len
input: cstring = "Hello World!"
gas: int64 = 200000
var
code_hash: evm_uint256be
value: evm_uint256be
address: evm_address
code_hash.bytes[0..2] = [1.uint8, 2, 3]
value.bytes[0..1] = [1.uint8, 0]
address.bytes[0..2] = [1.uint8, 2, 3]
var fn_table: ref evm_context_fn_table # Note fn_table will be garbage collected at the end of main function
# as we will always use it as ptr and not ref
new fn_table
fn_table[] = ctx_fn_table
var ctx = evm_context(fn_table: cast[ptr evm_context_fn_table](fn_table))
var msg = evm_message(
destination: address, sender: address, value: value, input_data: cast[ptr uint8](input),
input_size: sizeof(input), code_hash: code_hash, gas: gas, depth:0
)
var result = jit.execute(jit, addr ctx, EVM_HOMESTEAD, addr msg, cast[ptr uint8](code), code_size)
echo "Execution result:\n"
if result.status_code != EVM_SUCCESS:
echo " EVM execution failure: ", $result.status_code
else:
echo " Gas used: ", $(gas - result.gas_left)
echo " Gas left: ", $result.gas_left
echo " Output size: ", $(gas - result.gas_left)
echo "\n Output: "
var output = ""
for i in 0 ..< result.output_size:
output.add cast[char](cast[ptr UncheckedArray[uint8]](result.output_data)[i])
echo output
if not result.release.isNil:
result.release(addr result)
jit.destroy(jit)
main()

107
examples/examplevm.nim Normal file
View File

@ -0,0 +1,107 @@
# Port of https://github.com/ethereum/evmjit/blob/develop/examples/examplevm.c
# to Nim language
import ../src/evmjit, strutils
proc c_malloc(size: csize): pointer {.
importc: "malloc", header: "<stdlib.h>".}
proc c_calloc(num, size: csize): pointer {.
importc: "calloc", header: "<stdlib.h>".}
proc c_free(p: pointer) {.
importc: "free", header: "<stdlib.h>".}
type ExampleVM = object
instance: evm_instance
verbose: bool
proc evm_destroy(evm: ptr evm_instance) {.cdecl.}=
c_free evm
# Example options
proc evm_set_option(instance: ptr evm_instance, name: cstring, value: cstring): cint {.cdecl.}=
var vm = ExampleVM(instance: instance[])
if name == "verbose":
vm.verbose = ($name).parseBool
# Note: we don't return 1 if not a number or in int range as Nim will throw an exception instead
return 0
proc evm_release_result(r: ptr evm_result) {.cdecl.}=
r[] = evm_result() # Create a new empty evm_result
proc free_result_output_data(r: ptr evm_result) {.cdecl.}=
c_free r.output_data
proc execute(instance: ptr evm_instance; context: ptr evm_context;
rev: evm_revision; msg: ptr evm_message; code: ptr uint8;
code_size: csize): evm_result {.cdecl.}=
if code_size == 0:
# In case of empty code return a fancy error message
let error: cstring = if rev == EVM_BYZANTIUM: "Welcome to Byzantium"
else: "Hello Ethereum"
result.output_data = cast[ptr uint8](error)
result.output_size = error.len
result.status_code = EVM_FAILURE
result.release = nil # We don't need to release the constant messages
return
let vm: ptr ExampleVM = cast[ptr ExampleVM](instance) # So much hacks in original code :/
# Simulate executing by checking for some code patterns.
# Solidity inline assemble is used in the examples instead of EVM bytecode.
# Assembly: `{ mstore(0, address()) return(0, msize()) }`.
const return_address = "30600052596000f3"
# Assembly: `{ sstore(0, add(sload(0), 1)) }`
const counter = "600160005401600055"
echo "Debug: code_size = ", $code_size
echo "Debug: return_address.len = ", $return_address.len
echo "Debug: counter.len = ", $counter.len
echo "Debug: $cast[cstring](code) == return_address - ", $($cast[cstring](code) == return_address)
echo "Debug: $cast[cstring](code) == counter - ", $($cast[cstring](code) == counter)
echo "\n"
if code_size == return_address.len and $cast[cstring](code) == return_address:
let address_size = sizeof(msg.destination)
var output_data = cast[ptr uint8](c_malloc(address_size))
if output_data == nil:
result.status_code = EVM_INTERNAL_ERROR
return
copyMem(output_data, addr msg.destination, address_size)
result.status_code = EVM_SUCCESS
result.output_data = output_data
result.output_size = address_size
result.release = free_result_output_data
return
elif code_size == counter.len and $cast[cstring](code) == counter:
var value: evm_uint256be
var index = evm_uint256be() # Need var to have an address. Initialized to all 0 by default
context.fn_table.get_storage(addr value, context, addr msg.destination, addr index)
value.bytes[31] += 1
context.fn_table.set_storage(context, addr msg.destination, addr index, addr value)
result.status_code = EVM_SUCCESS
return
result.release = evm_release_result
result.status_code = EVM_FAILURE
result.gas_left = 0
if vm.verbose:
echo "Execution done.\n"
proc examplevm_create*(): ptr evm_instance =
var init = evm_instance(
abi_version: EVM_ABI_VERSION,
destroy: evm_destroy,
execute: execute,
set_option: evm_set_option
)
let vm = cast[ptr ExampleVM](c_calloc(1, sizeof(ExampleVM)))
result = addr vm.instance
copyMem(result, addr init, sizeof(init))