mirror of
https://github.com/status-im/evmc.git
synced 2025-02-23 08:28:15 +00:00
Add C-API example
This commit is contained in:
parent
b3bd9d5e01
commit
9c384a5e9a
155
examples/capi.nim
Normal file
155
examples/capi.nim
Normal 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
107
examples/examplevm.nim
Normal 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))
|
Loading…
x
Reference in New Issue
Block a user