-
Notifications
You must be signed in to change notification settings - Fork 314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Go bindings #41
Go bindings #41
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 !windows LDFLAGS: -ldl | ||
|
||
#include <evmc/evmc.h> | ||
#include <evmc/helpers.h> | ||
#include <evmc/loader.h> | ||
|
||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not keep the named version? Can you have named members here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm counting on compiler warning in case a field is missed. |
||
}; | ||
|
||
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) | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also add "asserts" for:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assertion checks the size of the types of these. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the underlying type. Not sure if we want to ensure here that those fields are actually of the underlying type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has been reworked. |
||
|
||
type Error int32 | ||
|
||
func (err Error) IsInternalError() bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to be used below (or anywhere?). |
||
return err < 0 | ||
} | ||
|
||
func (err Error) Error() string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would a name like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is implementation of type error interface {
Error() string
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok, so all the wrappers return a standard There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It means the |
||
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))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure it is a good idea making this a panic? |
||
} | ||
|
||
const ( | ||
Failure = Error(C.EVMC_FAILURE) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why consts only for two status codes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm waiting for a comment from Go devs, because sometimes they predefined errors as consts and sometimes create errors at hoc. |
||
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++ { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we assert that Is there an option for static (compile time) assert in Go? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no static assert. You can't even check the struct size (without "unsafe" package). But this is not a big problem here, because the array access is safe (will panic if wrong, but I have not checked if the go compiler is able to optimize this). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has been fixed by the assertion commit. |
||
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++ { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has been fixed by the assertion commit. |
||
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])) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../include/evmc/evmc.h |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is this used? What file is it loading? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are unit tests. You can run them with |
||
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()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../include/evmc/helpers.h |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* EVMC: Ethereum Client-VM Connector API. | ||
* Copyright 2018 Pawel Bylica. | ||
* Licensed under the MIT License. See the LICENSE file. | ||
*/ | ||
|
||
#include "_cgo_export.h" | ||
|
||
#include <stdlib.h> | ||
|
||
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, | ||
}; | ||
|
||
void evmc_go_free_result_output(const struct evmc_result* result) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually instead of this one could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually not. This is the implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, too early. |
||
{ | ||
free((void*)result->output_data); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this now have
create2_salt
parameter?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah it's only for calls...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be passed back to the Host interface. I have not done this yet.