Skip to content

Commit

Permalink
Add Go bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
chfast committed Jul 13, 2018
1 parent 5027184 commit d946800
Show file tree
Hide file tree
Showing 4 changed files with 524 additions and 0 deletions.
258 changes: 258 additions & 0 deletions bindings/go/evmc/evmc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// EVMC: Ethereum Client-VM Connector API.
// Copyright 2018 Pawel Bylica.
// Licensed under the MIT License. See the LICENSE file.

package evmc

/*
#cgo CFLAGS: -I${SRCDIR}/../../../include
#cgo LDFLAGS: -ldl
#include <evmc/evmc.h>
#include <evmc/helpers.h>
#include <evmc/loader.h>
#include <stdlib.h>
#include <string.h>
inline int set_option(struct evmc_instance* instance, _GoString_ name, _GoString_ value)
{
if (instance->set_option)
return instance->set_option(instance, name.p, value.p);
return -1;
}
struct extended_context
{
struct evmc_context context;
int64_t index;
};
extern const struct evmc_context_fn_table fn_table;
static struct evmc_result execute_wrapper(struct evmc_instance* instance,
int64_t context_index,
enum evmc_revision rev,
_GoBytes_ destination,
_GoBytes_ sender,
_GoBytes_ value,
_GoBytes_ input,
_GoBytes_ code_hash,
int64_t gas,
int32_t depth,
uint32_t flags,
_GoBytes_ code)
{
struct evmc_message msg;
msg.input_data = input.p;
msg.input_size = input.n;
msg.gas = gas;
msg.depth = depth;
msg.kind = EVMC_CALL;
msg.flags = flags;
memcpy(msg.destination.bytes, destination.p, sizeof(msg.destination));
memcpy(msg.sender.bytes, sender.p, sizeof(msg.sender));
memcpy(msg.value.bytes, value.p, sizeof(msg.value));
memcpy(msg.code_hash.bytes, code_hash.p, sizeof(msg.code_hash));
struct extended_context ctx = {{&fn_table}, context_index};
return instance->execute(instance, &ctx.context, rev, &msg, code.p, code.n);
}
*/
import "C"

import (
"errors"
"fmt"
"sync"
"unsafe"

"github.com/ethereum/go-ethereum/common"
)

type Error int32

func (err Error) IsInternalError() bool {
return err < 0
}

func (err Error) Error() string {
code := C.enum_evmc_status_code(err)

switch code {
case C.EVMC_FAILURE:
return "evmc: failure"
case C.EVMC_REVERT:
return "evmc: revert"
case C.EVMC_OUT_OF_GAS:
return "evmc: out of gas"
case C.EVMC_INVALID_INSTRUCTION:
return "evmc: invalid instruction"
case C.EVMC_UNDEFINED_INSTRUCTION:
return "evmc: undefined instruction"
case C.EVMC_STACK_OVERFLOW:
return "evmc: stack overflow"
case C.EVMC_STACK_UNDERFLOW:
return "evmc: stack underflow"
case C.EVMC_BAD_JUMP_DESTINATION:
return "evmc: bad jump destination"
case C.EVMC_INVALID_MEMORY_ACCESS:
return "evmc: invalid memory access"
case C.EVMC_CALL_DEPTH_EXCEEDED:
return "evmc: call depth exceeded"
case C.EVMC_STATIC_MODE_VIOLATION:
return "evmc: static mode violation"
case C.EVMC_PRECOMPILE_FAILURE:
return "evmc: precompile failure"
case C.EVMC_CONTRACT_VALIDATION_FAILURE:
return "evmc: contract validation failure"
case C.EVMC_INTERNAL_ERROR:
return "evmc: internal error"
case C.EVMC_REJECTED:
return "evmc: rejected"
}

if code < 0 {
return fmt.Sprintf("evmc: unknown internal error (%d)", int32(code))
}

panic(fmt.Sprintf("evmc: unknown status code %d", int32(code)))
}

const (
Failure = Error(C.EVMC_FAILURE)
Revert = Error(C.EVMC_REVERT)
)

type Revision int32

const (
Frontier = C.EVMC_FRONTIER
Homestead = C.EVMC_HOMESTEAD
TangerineWhistle = C.EVMC_TANGERINE_WHISTLE
SpuriousDragon = C.EVMC_SPURIOUS_DRAGON
Byzantium = C.EVMC_BYZANTIUM
Constantinople = C.EVMC_CONSTANTINOPLE
)

type Instance struct {
handle *C.struct_evmc_instance
}

func Load(filename string) (instance *Instance, err error) {
cfilename := C.CString(filename)
var loaderErr C.enum_evmc_loader_error_code
handle := C.evmc_load_and_create(cfilename, &loaderErr)
C.free(unsafe.Pointer(cfilename))
switch loaderErr {
case C.EVMC_LOADER_SUCCESS:
instance = &Instance{handle}
case C.EVMC_LOADER_CANNOT_OPEN:
err = fmt.Errorf("evmc loader: cannot open %s", filename)
case C.EVMC_LOADER_SYMBOL_NOT_FOUND:
err = fmt.Errorf("evmc loader: the EVMC create function not found in %s", filename)
case C.EVMC_LOADER_INVALID_ARGUMENT:
panic("evmc loader: filename argument is empty")
case C.EVMC_LOADER_INSTANCE_CREATION_FAILURE:
err = errors.New("evmc loader: VM instance creation failure")
case C.EVMC_LOADER_ABI_VERSION_MISMATCH:
err = errors.New("evmc loader: ABI version mismatch")
default:
panic(fmt.Sprintf("evmc loader: unexpected error (%d)", loaderErr))
}
return instance, err
}

func (instance *Instance) Destroy() {
C.evmc_destroy(instance.handle)
}

func (instance *Instance) Name() string {
return C.GoString(instance.handle.name)
}

func (instance *Instance) Version() string {
return C.GoString(instance.handle.version)
}

func (instance *Instance) SetOption(name string, value string) (err error) {
r := C.set_option(instance.handle, name+"\x00", value+"\x00")
if r == -1 {
err = errors.New("evmc: VM does not accept any options")
} else if r != 0 {
err = fmt.Errorf("evmc: option '%s' not accepted", name)
}
return err
}

func (instance *Instance) Execute(ctx HostContext, rev Revision,
destination common.Address, sender common.Address, value common.Hash, input []byte, codeHash common.Hash, gas int64, depth int, static bool,
code []byte) (output []byte, gasLeft int64, err error) {

flags := C.uint32_t(0)
if static {
flags |= C.EVMC_STATIC
}

ctxId := addHostContext(ctx)
r := C.execute_wrapper(instance.handle, C.int64_t(ctxId), uint32(rev), destination[:], sender[:], value[:], input,
codeHash[:], C.int64_t(gas), C.int32_t(depth), flags, code)
removeHostContext(ctxId)

output = C.GoBytes(unsafe.Pointer(r.output_data), C.int(r.output_size))
gasLeft = int64(r.gas_left)
if r.status_code != C.EVMC_SUCCESS {
err = Error(r.status_code)
}

if r.release != nil {
C.evmc_release_result(&r)
}

return output, gasLeft, err
}

var (
hostContextCounter int
hostContextMap = map[int]HostContext{}
hostContextMapMu sync.Mutex
)

func addHostContext(ctx HostContext) int {
hostContextMapMu.Lock()
id := hostContextCounter
hostContextCounter++
hostContextMap[id] = ctx
hostContextMapMu.Unlock()
return id
}

func removeHostContext(id int) {
hostContextMapMu.Lock()
delete(hostContextMap, id)
hostContextMapMu.Unlock()
}

func getHostContext(idx int) HostContext {
hostContextMapMu.Lock()
ctx := hostContextMap[idx]
hostContextMapMu.Unlock()
return ctx
}

func evmcUint256be(in common.Hash) C.struct_evmc_uint256be {
out := C.struct_evmc_uint256be{}
for i := 0; i < len(in); i++ {
out.bytes[i] = C.uint8_t(in[i])
}
return out
}

func evmcAddress(address common.Address) C.struct_evmc_address {
r := C.struct_evmc_address{}
for i := 0; i < len(address); i++ {
r.bytes[i] = C.uint8_t(address[i])
}
return r
}
24 changes: 24 additions & 0 deletions bindings/go/evmc/evmc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// EVMC: Ethereum Client-VM Connector API.
// Copyright 2018 Pawel Bylica.
// Licensed under the MIT License. See the LICENSE file.

package evmc

import (
"os"
"testing"
)

func TestLoad(t *testing.T) {
i, err := Load(os.Getenv("EVMC_PATH"))
if err != nil {
t.Fatal(err.Error())
}
defer i.Destroy()
if i.Name() != "interpreter" {
t.Fatal("name is not 'interpreter'")
}
if i.Version()[0] != '1' {
t.Fatalf("version is %s", i.Version())
}
}
22 changes: 22 additions & 0 deletions bindings/go/evmc/host.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "_cgo_export.h"

#include <stdlib.h>

const struct evmc_context_fn_table fn_table = {
(evmc_account_exists_fn) accountExists,
(evmc_get_storage_fn) getStorage,
(evmc_set_storage_fn) setStorage,
(evmc_get_balance_fn) getBalance,
(evmc_get_code_size_fn) getCodeSize,
(evmc_copy_code_fn) copyCode,
(evmc_selfdestruct_fn) selfdestruct,
(evmc_call_fn) call,
(evmc_get_tx_context_fn) getTxContext,
(evmc_get_block_hash_fn) getBlockHash,
(evmc_emit_log_fn) emitLog,
};

void free_result_output(const struct evmc_result* result)
{
free((void*)result->output_data);
}
Loading

0 comments on commit d946800

Please sign in to comment.