diff --git a/bindings/go/evmc/evmc.go b/bindings/go/evmc/evmc.go new file mode 100644 index 000000000..6b419d523 --- /dev/null +++ b/bindings/go/evmc/evmc.go @@ -0,0 +1,249 @@ +// 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 +#include +#include + +#include +#include + +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 +} diff --git a/bindings/go/evmc/evmc_test.go b/bindings/go/evmc/evmc_test.go new file mode 100644 index 000000000..244002654 --- /dev/null +++ b/bindings/go/evmc/evmc_test.go @@ -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()) + } +} diff --git a/bindings/go/evmc/host.c b/bindings/go/evmc/host.c new file mode 100644 index 000000000..1d2640b77 --- /dev/null +++ b/bindings/go/evmc/host.c @@ -0,0 +1,27 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2018 Pawel Bylica. + * Licensed under the MIT License. See the LICENSE file. + */ + +#include "_cgo_export.h" + +#include + +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); +} diff --git a/bindings/go/evmc/host.go b/bindings/go/evmc/host.go new file mode 100644 index 000000000..0b5dfb9cf --- /dev/null +++ b/bindings/go/evmc/host.go @@ -0,0 +1,220 @@ +// 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 + +#include + +struct extended_context +{ + struct evmc_context context; + int64_t index; +}; + +void free_result_output(const struct evmc_result* result); + +*/ +import "C" +import ( + "math/big" + "unsafe" + + "github.com/ethereum/go-ethereum/common" +) + +type CallKind int + +const ( + Call = C.EVMC_CALL + DelegateCall = C.EVMC_DELEGATECALL + CallCode = C.EVMC_CALLCODE + Create = C.EVMC_CREATE +) + +func goAddress(in C.struct_evmc_address) common.Address { + out := common.Address{} + for i := 0; i < len(out); i++ { + out[i] = byte(in.bytes[i]) + } + return out +} + +func goHash(in C.struct_evmc_uint256be) common.Hash { + out := common.Hash{} + for i := 0; i < len(out); i++ { + out[i] = byte(in.bytes[i]) + } + return out +} + +func goByteSlice(data *C.uint8_t, size C.size_t) []byte { + if size == 0 { + return []byte{} + } + return (*[1 << 30]byte)(unsafe.Pointer(data))[:size:size] +} + +type HostContext interface { + AccountExists(addr common.Address) bool + GetStorage(addr common.Address, key common.Hash) common.Hash + SetStorage(addr common.Address, key common.Hash, value common.Hash) + GetBalance(addr common.Address) common.Hash + GetCodeSize(addr common.Address) int + GetCode(addr common.Address) []byte + Selfdestruct(addr common.Address, beneficiary common.Address) + GetTxContext() (gasPrice common.Hash, origin common.Address, coinbase common.Address, number int64, timestamp int64, + gasLimit int64, difficulty common.Hash) + GetBlockHash(number int64) common.Hash + EmitLog(addr common.Address, topics []common.Hash, data []byte) + Call(kind CallKind, + destination common.Address, sender common.Address, value *big.Int, input []byte, gas int64, depth int, + static bool) (output []byte, gasLeft int64, createAddr common.Address, err error) +} + +//export accountExists +func accountExists(pCtx unsafe.Pointer, pAddr *C.struct_evmc_address) C.int { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + exists := ctx.AccountExists(goAddress(*pAddr)) + r := C.int(0) + if exists { + r = 1 + } + return r +} + +//export getStorage +func getStorage(pResult *C.struct_evmc_uint256be, pCtx unsafe.Pointer, pAddr *C.struct_evmc_address, pKey *C.struct_evmc_uint256be) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + value := ctx.GetStorage(goAddress(*pAddr), goHash(*pKey)) + *pResult = evmcUint256be(value) +} + +//export setStorage +func setStorage(pCtx unsafe.Pointer, pAddr *C.struct_evmc_address, pKey *C.struct_evmc_uint256be, pVal *C.struct_evmc_uint256be) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + ctx.SetStorage(goAddress(*pAddr), goHash(*pKey), goHash(*pVal)) +} + +//export getBalance +func getBalance(pResult *C.struct_evmc_uint256be, pCtx unsafe.Pointer, pAddr *C.struct_evmc_address) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + balance := ctx.GetBalance(goAddress(*pAddr)) + *pResult = evmcUint256be(balance) +} + +//export getCodeSize +func getCodeSize(pCtx unsafe.Pointer, pAddr *C.struct_evmc_address) C.size_t { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + return C.size_t(ctx.GetCodeSize(goAddress(*pAddr))) +} + +//export copyCode +func copyCode(pCtx unsafe.Pointer, pAddr *C.struct_evmc_address, offset C.size_t, p *C.uint8_t, size C.size_t) C.size_t { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + code := ctx.GetCode(goAddress(*pAddr)) + length := C.size_t(len(code)) + + if offset >= length { + return 0 + } + + toCopy := length - offset + if toCopy > size { + toCopy = size + } + + out := goByteSlice(p, size) + copy(out, code[offset:]) + return toCopy +} + +//export selfdestruct +func selfdestruct(pCtx unsafe.Pointer, pAddr *C.struct_evmc_address, pBeneficiary *C.struct_evmc_address) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + ctx.Selfdestruct(goAddress(*pAddr), goAddress(*pBeneficiary)) +} + +//export getTxContext +func getTxContext(pResult unsafe.Pointer, pCtx unsafe.Pointer) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + + gasPrice, origin, coinbase, number, timestamp, gasLimit, difficulty := ctx.GetTxContext() + + *(*C.struct_evmc_tx_context)(pResult) = C.struct_evmc_tx_context{ + evmcUint256be(gasPrice), + evmcAddress(origin), + evmcAddress(coinbase), + C.int64_t(number), + C.int64_t(timestamp), + C.int64_t(gasLimit), + evmcUint256be(difficulty), + } +} + +//export getBlockHash +func getBlockHash(pResult *C.struct_evmc_uint256be, pCtx unsafe.Pointer, number int64) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + + *pResult = evmcUint256be(ctx.GetBlockHash(number)) +} + +//export emitLog +func emitLog(pCtx unsafe.Pointer, pAddr *C.struct_evmc_address, pData unsafe.Pointer, dataSize C.size_t, pTopics unsafe.Pointer, topicsCount C.size_t) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + + // FIXME: Optimize memory copy + data := C.GoBytes(pData, C.int(dataSize)) + tData := C.GoBytes(pTopics, C.int(topicsCount*32)) + + nTopics := int(topicsCount) + topics := make([]common.Hash, nTopics) + for i := 0; i < nTopics; i++ { + copy(topics[i][:], tData[i*32:(i+1)*32]) + } + + ctx.EmitLog(goAddress(*pAddr), topics, data) +} + +//export call +func call(pResult *C.struct_evmc_result, pCtx unsafe.Pointer, msg *C.struct_evmc_message) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + + kind := CallKind(msg.kind) + output, gasLeft, createAddr, err := ctx.Call(kind, goAddress(msg.destination), goAddress(msg.sender), goHash(msg.value).Big(), + goByteSlice(msg.input_data, msg.input_size), int64(msg.gas), int(msg.depth), msg.flags != 0) + + statusCode := C.enum_evmc_status_code(0) + if err != nil { + statusCode = C.enum_evmc_status_code(err.(Error)) + } + + result := C.struct_evmc_result{} + result.status_code = statusCode + result.gas_left = C.int64_t(gasLeft) + result.create_address = evmcAddress(createAddr) + + if len(output) > 0 { + // TODO: We could pass it directly to the caller without a copy. The caller should release it. Check depth. + cOutput := C.CBytes(output) + result.output_data = (*C.uint8_t)(cOutput) + result.output_size = C.size_t(len(output)) + result.release = (C.evmc_release_result_fn)(C.free_result_output) + } + + *pResult = result +} diff --git a/bindings/go/evmc/loader.c b/bindings/go/evmc/loader.c new file mode 100644 index 000000000..77d607503 --- /dev/null +++ b/bindings/go/evmc/loader.c @@ -0,0 +1,6 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2018 Pawel Bylica. + * Licensed under the MIT License. See the LICENSE file. + */ + +#include "../../../lib/loader/loader.c"