diff --git a/bindings/go/evmc/evmc.go b/bindings/go/evmc/evmc.go new file mode 100644 index 000000000..ceba737fe --- /dev/null +++ b/bindings/go/evmc/evmc.go @@ -0,0 +1,273 @@ +// 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}/.. -Wall -Wextra +#cgo LDFLAGS: -ldl + +#include +#include +#include + +#include +#include + +static inline int set_option(struct evmc_instance* instance, char* name, char* value) +{ + int ret = evmc_set_option(instance, name, value); + free(name); + free(value); + return ret; +} + +struct extended_context +{ + struct evmc_context context; + int64_t index; +}; + +extern const struct evmc_context_fn_table evmc_go_fn_table; + +static struct evmc_result execute_wrapper(struct evmc_instance* instance, int64_t context_index, enum evmc_revision rev, + const struct evmc_address* destination, const struct evmc_address* sender, const struct evmc_uint256be* value, + const uint8_t* input_data, size_t input_size, const struct evmc_uint256be* code_hash, int64_t gas, + int32_t depth, enum evmc_call_kind kind, uint32_t flags, const uint8_t* code, size_t code_size) +{ + struct evmc_uint256be create2_salt = {}; + struct evmc_message msg = { + *destination, + *sender, + *value, + input_data, + input_size, + *code_hash, + create2_salt, + gas, + depth, + kind, + flags, + }; + + struct extended_context ctx = {{&evmc_go_fn_table}, context_index}; + return instance->execute(instance, &ctx.context, rev, &msg, code, code_size); +} +*/ +import "C" + +import ( + "errors" + "fmt" + "sync" + "unsafe" + + "github.com/ethereum/go-ethereum/common" +) + +// Static asserts. +const ( + _ = uint(common.HashLength - C.sizeof_struct_evmc_uint256be) // The size of evmc_uint256be equals the size of Hash. + _ = uint(C.sizeof_struct_evmc_uint256be - common.HashLength) + _ = uint(common.AddressLength - C.sizeof_struct_evmc_address) // The size of evmc_address equals the size of Address. + _ = uint(C.sizeof_struct_evmc_address - common.AddressLength) +) + +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 Revision = C.EVMC_FRONTIER + Homestead Revision = C.EVMC_HOMESTEAD + TangerineWhistle Revision = C.EVMC_TANGERINE_WHISTLE + SpuriousDragon Revision = C.EVMC_SPURIOUS_DRAGON + Byzantium Revision = C.EVMC_BYZANTIUM + Constantinople Revision = 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 invalid") + 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)", int(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, C.CString(name), C.CString(value)) + if r != 1 { + 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, kind CallKind, static bool, code []byte) (output []byte, gasLeft int64, err error) { + + flags := C.uint32_t(0) + if static { + flags |= C.EVMC_STATIC + } + + ctxId := addHostContext(ctx) + // FIXME: Clarify passing by pointer vs passing by value. + evmcDestination := evmcAddress(destination) + evmcSender := evmcAddress(sender) + evmcValue := evmcUint256be(value) + evmcCodeHash := evmcUint256be(codeHash) + result := C.execute_wrapper(instance.handle, C.int64_t(ctxId), uint32(rev), &evmcDestination, &evmcSender, &evmcValue, + bytesPtr(input), C.size_t(len(input)), &evmcCodeHash, C.int64_t(gas), C.int32_t(depth), C.enum_evmc_call_kind(kind), + flags, bytesPtr(code), C.size_t(len(code))) + removeHostContext(ctxId) + + output = C.GoBytes(unsafe.Pointer(result.output_data), C.int(result.output_size)) + gasLeft = int64(result.gas_left) + if result.status_code != C.EVMC_SUCCESS { + err = Error(result.status_code) + } + + if result.release != nil { + C.evmc_release_result(&result) + } + + 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 +} + +func bytesPtr(bytes []byte) *C.uint8_t { + if len(bytes) == 0 { + return nil + } + return (*C.uint8_t)(unsafe.Pointer(&bytes[0])) +} diff --git a/bindings/go/evmc/evmc.h b/bindings/go/evmc/evmc.h new file mode 120000 index 000000000..68e4b5bee --- /dev/null +++ b/bindings/go/evmc/evmc.h @@ -0,0 +1 @@ +../../../include/evmc/evmc.h \ No newline at end of file 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/helpers.h b/bindings/go/evmc/helpers.h new file mode 120000 index 000000000..9effd04b4 --- /dev/null +++ b/bindings/go/evmc/helpers.h @@ -0,0 +1 @@ +../../../include/evmc/helpers.h \ No newline at end of file diff --git a/bindings/go/evmc/host.c b/bindings/go/evmc/host.c new file mode 100644 index 000000000..5eb0b592c --- /dev/null +++ b/bindings/go/evmc/host.c @@ -0,0 +1,29 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2018 Pawel Bylica. + * Licensed under the MIT License. See the LICENSE file. + */ + +#include "_cgo_export.h" + +#include + +__attribute__((visibility("hidden"))) const struct evmc_context_fn_table evmc_go_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_get_code_hash_fn)getCodeHash, + (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, +}; + +__attribute__((visibility("hidden"))) void evmc_go_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..f9abb24e8 --- /dev/null +++ b/bindings/go/evmc/host.go @@ -0,0 +1,238 @@ +// 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}/.. -Wall -Wextra -Wno-unused-parameter + +#include + +struct extended_context +{ + struct evmc_context context; + int64_t index; +}; + +void evmc_go_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 CallKind = C.EVMC_CALL + DelegateCall CallKind = C.EVMC_DELEGATECALL + CallCode CallKind = C.EVMC_CALLCODE + Create CallKind = C.EVMC_CREATE + Create2 CallKind = C.EVMC_CREATE2 +) + +type StorageStatus int + +const ( + StorageUnchanged StorageStatus = C.EVMC_STORAGE_UNCHANGED + StorageModified StorageStatus = C.EVMC_STORAGE_MODIFIED + StorageAdded StorageStatus = C.EVMC_STORAGE_ADDED + StorageDeleted StorageStatus = C.EVMC_STORAGE_DELETED +) + +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) StorageStatus + GetBalance(addr common.Address) common.Hash + GetCodeSize(addr common.Address) int + GetCodeHash(addr common.Address) common.Hash + 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) C.enum_evmc_storage_status { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + return C.enum_evmc_storage_status(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 getCodeHash +func getCodeHash(pResult *C.struct_evmc_uint256be, pCtx unsafe.Pointer, pAddr *C.struct_evmc_address) { + idx := int((*C.struct_extended_context)(pCtx).index) + ctx := getHostContext(idx) + *pResult = evmcUint256be(ctx.GetCodeHash(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.evmc_go_free_result_output) + } + + *pResult = result +} diff --git a/bindings/go/evmc/loader.c b/bindings/go/evmc/loader.c new file mode 120000 index 000000000..37b452e9c --- /dev/null +++ b/bindings/go/evmc/loader.c @@ -0,0 +1 @@ +../../../lib/loader/loader.c \ No newline at end of file diff --git a/bindings/go/evmc/loader.h b/bindings/go/evmc/loader.h new file mode 120000 index 000000000..8e4511962 --- /dev/null +++ b/bindings/go/evmc/loader.h @@ -0,0 +1 @@ +../../../include/evmc/loader.h \ No newline at end of file diff --git a/circle.yml b/circle.yml index 051180e93..d3616cebf 100644 --- a/circle.yml +++ b/circle.yml @@ -82,11 +82,31 @@ jobs: git commit -m "Update docs" git push -f "https://$GITHUB_TOKEN@github.com/ethereum/evmc.git" HEAD:gh-pages + bindings-go-1.10: + docker: + - image: circleci/golang:1.10 + steps: &bindings-go-steps + - checkout + - run: + name: "Go Build" + command: | + go get -v github.com/ethereum/go-ethereum/common + go build -v ./bindings/go/evmc + go vet -v ./bindings/go/evmc + + bindings-go-1.9: + docker: + - image: circleci/golang:1.9 + steps: *bindings-go-steps + + workflows: version: 2 evmc: jobs: - build + - bindings-go-1.10 + - bindings-go-1.9 - test-docs - upload-docs: requires: