diff --git a/appveyor.yml b/appveyor.yml index 40b8a65..8ecc172 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -47,6 +47,8 @@ build_script: copy lib/loader/loader.c bindings/go/evmc go build ./bindings/go/evmc + go generate ./bindings/go/evmc + go test -v ./bindings/go/evmc } after_build: diff --git a/bindings/go/evmc/.gitignore b/bindings/go/evmc/.gitignore new file mode 100644 index 0000000..140f8cf --- /dev/null +++ b/bindings/go/evmc/.gitignore @@ -0,0 +1 @@ +*.so diff --git a/bindings/go/evmc/evmc.go b/bindings/go/evmc/evmc.go index fe3ffdc..6b3f66a 100644 --- a/bindings/go/evmc/evmc.go +++ b/bindings/go/evmc/evmc.go @@ -1,6 +1,6 @@ // EVMC: Ethereum Client-VM Connector API. -// Copyright 2018 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. See the LICENSE file. +// Copyright 2019 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. package evmc diff --git a/bindings/go/evmc/evmc_test.go b/bindings/go/evmc/evmc_test.go index db20b5d..88cbcf9 100644 --- a/bindings/go/evmc/evmc_test.go +++ b/bindings/go/evmc/evmc_test.go @@ -1,24 +1,49 @@ // EVMC: Ethereum Client-VM Connector API. -// Copyright 2018 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. See the LICENSE file. +// Copyright 2019 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. + +//go:generate gcc -shared ../../../examples/example_vm.c -I../../../include -o example_vm.so package evmc import ( - "os" + "bytes" "testing" + + "github.com/ethereum/go-ethereum/common" ) +var modulePath = "./example_vm.so" + func TestLoad(t *testing.T) { - i, err := Load(os.Getenv("EVMC_PATH")) + i, err := Load(modulePath) if err != nil { t.Fatal(err.Error()) } defer i.Destroy() - if i.Name() != "interpreter" { - t.Fatal("name is not 'interpreter'") + if i.Name() != "example_vm" { + t.Fatalf("name is %s", i.Name()) } - if i.Version()[0] != '1' { - t.Fatalf("version is %s", i.Version()) + if i.Version()[0] < '0' || i.Version()[0] > '9' { + t.Fatalf("version number is weird: %s", i.Version()) + } +} + +func TestExecute(t *testing.T) { + vm, _ := Load(modulePath) + defer vm.Destroy() + + addr := common.Address{} + h := common.Hash{} + output, gasLeft, err := vm.Execute(nil, Byzantium, Call, false, 1, 999, addr, addr, nil, h, nil, h) + + if bytes.Compare(output, []byte("Welcome to Byzantium!")) != 0 { + t.Errorf("execution unexpected output: %s", output) + } + if gasLeft != 99 { + t.Error("execution gas left is incorrect") + } + if err != Failure { + t.Error("execution returned unexpected error") } } diff --git a/bindings/go/evmc/host.go b/bindings/go/evmc/host.go index 9ca492d..129542e 100644 --- a/bindings/go/evmc/host.go +++ b/bindings/go/evmc/host.go @@ -1,6 +1,6 @@ // EVMC: Ethereum Client-VM Connector API. -// Copyright 2018 The EVMC Authors. -// Licensed under the Apache License, Version 2.0. See the LICENSE file. +// Copyright 2019 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. package evmc @@ -69,6 +69,17 @@ func goByteSlice(data *C.uint8_t, size C.size_t) []byte { return (*[1 << 30]byte)(unsafe.Pointer(data))[:size:size] } +// TxContext contains information about current transaction and block. +type TxContext struct { + GasPrice common.Hash + Origin common.Address + Coinbase common.Address + Number int64 + Timestamp int64 + GasLimit int64 + Difficulty common.Hash +} + type HostContext interface { AccountExists(addr common.Address) bool GetStorage(addr common.Address, key common.Hash) common.Hash @@ -78,8 +89,7 @@ type HostContext interface { 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) + GetTxContext() TxContext GetBlockHash(number int64) common.Hash EmitLog(addr common.Address, topics []common.Hash, data []byte) Call(kind CallKind, @@ -162,16 +172,16 @@ func getTxContext(pCtx unsafe.Pointer) C.struct_evmc_tx_context { idx := int((*C.struct_extended_context)(pCtx).index) ctx := getHostContext(idx) - gasPrice, origin, coinbase, number, timestamp, gasLimit, difficulty := ctx.GetTxContext() + txContext := ctx.GetTxContext() return C.struct_evmc_tx_context{ - evmcBytes32(gasPrice), - evmcAddress(origin), - evmcAddress(coinbase), - C.int64_t(number), - C.int64_t(timestamp), - C.int64_t(gasLimit), - evmcBytes32(difficulty), + evmcBytes32(txContext.GasPrice), + evmcAddress(txContext.Origin), + evmcAddress(txContext.Coinbase), + C.int64_t(txContext.Number), + C.int64_t(txContext.Timestamp), + C.int64_t(txContext.GasLimit), + evmcBytes32(txContext.Difficulty), } } diff --git a/bindings/go/evmc/host_test.go b/bindings/go/evmc/host_test.go new file mode 100644 index 0000000..e02cb23 --- /dev/null +++ b/bindings/go/evmc/host_test.go @@ -0,0 +1,90 @@ +// EVMC: Ethereum Client-VM Connector API. +// Copyright 2019 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. + +package evmc + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +type testHostContext struct{} + +func (host *testHostContext) AccountExists(addr common.Address) bool { + return false +} + +func (host *testHostContext) GetStorage(addr common.Address, key common.Hash) common.Hash { + return common.Hash{} +} + +func (host *testHostContext) SetStorage(addr common.Address, key common.Hash, value common.Hash) (status StorageStatus) { + return StorageUnchanged +} + +func (host *testHostContext) GetBalance(addr common.Address) common.Hash { + return common.Hash{} +} + +func (host *testHostContext) GetCodeSize(addr common.Address) int { + return 0 +} + +func (host *testHostContext) GetCodeHash(addr common.Address) common.Hash { + return common.Hash{} +} + +func (host *testHostContext) GetCode(addr common.Address) []byte { + return nil +} + +func (host *testHostContext) Selfdestruct(addr common.Address, beneficiary common.Address) { +} + +func (host *testHostContext) GetTxContext() TxContext { + txContext := TxContext{} + txContext.Number = 42 + return txContext +} + +func (host *testHostContext) GetBlockHash(number int64) common.Hash { + return common.Hash{} +} + +func (host *testHostContext) EmitLog(addr common.Address, topics []common.Hash, data []byte) { +} + +func (host *testHostContext) Call(kind CallKind, + destination common.Address, sender common.Address, value *big.Int, input []byte, gas int64, depth int, + static bool, salt *big.Int) (output []byte, gasLeft int64, createAddr common.Address, err error) { + return nil, gas, common.Address{}, nil +} + +func TestGetTxContext(t *testing.T) { + vm, _ := Load(modulePath) + defer vm.Destroy() + + host := &testHostContext{} + code := []byte("\x43\x60\x00\x52\x59\x60\x00\xf3") + + addr := common.Address{} + h := common.Hash{} + output, gasLeft, err := vm.Execute(host, Byzantium, Call, false, 1, 100, addr, addr, nil, h, code, h) + + if len(output) != 20 { + t.Errorf("unexpected output size: %d", len(output)) + } + if bytes.Compare(output[0:3], []byte("42\x00")) != 0 { + t.Errorf("execution unexpected output: %s", output) + } + if gasLeft != 50 { + t.Errorf("execution gas left is incorrect: %x", gasLeft) + } + if err != nil { + t.Error("execution returned unexpected error") + } +} diff --git a/circle.yml b/circle.yml index 39e5a94..e8587ab 100644 --- a/circle.yml +++ b/circle.yml @@ -147,6 +147,8 @@ jobs: go get -v github.com/ethereum/go-ethereum/common go build -v ./bindings/go/evmc go vet -v ./bindings/go/evmc + go generate -v ./bindings/go/evmc + go test -v ./bindings/go/evmc bindings-go-1.9: docker: diff --git a/examples/example_vm.c b/examples/example_vm.c index a13bab9..e4f8729 100644 --- a/examples/example_vm.c +++ b/examples/example_vm.c @@ -91,6 +91,7 @@ static struct evmc_result execute(struct evmc_instance* instance, ret.output_data = (const uint8_t*)error; ret.output_size = strlen(error); ret.status_code = EVMC_FAILURE; + ret.gas_left = msg->gas / 10; ret.release = NULL; // We don't need to release the constant messages. return ret; } @@ -106,7 +107,10 @@ static struct evmc_result execute(struct evmc_instance* instance, // Assembly: `{ sstore(0, add(sload(0), 1)) }` const char counter[] = "\x60\x01\x60\x00\x54\x01\x60\x00\x55"; - if (code_size == strlen(return_address) && + // Assembly: `{ mstore(0, number()) return(0, msize()) }` + const char return_block_number[] = "\x43\x60\x00\x52\x59\x60\x00\xf3"; + + if (code_size == (sizeof(return_address) - 1) && strncmp((const char*)code, return_address, code_size) == 0) { static const size_t address_size = sizeof(msg->destination); @@ -124,7 +128,8 @@ static struct evmc_result execute(struct evmc_instance* instance, ret.release = &free_result_output_data; return ret; } - else if (code_size == strlen(counter) && strncmp((const char*)code, counter, code_size) == 0) + else if (code_size == (sizeof(counter) - 1) && + strncmp((const char*)code, counter, code_size) == 0) { const evmc_bytes32 key = {{0}}; evmc_bytes32 value = context->host->get_storage(context, &msg->destination, &key); @@ -133,6 +138,21 @@ static struct evmc_result execute(struct evmc_instance* instance, ret.status_code = EVMC_SUCCESS; return ret; } + else if (code_size == (sizeof(return_block_number) - 1) && + strncmp((const char*)code, return_block_number, code_size) == 0) + { + const struct evmc_tx_context tx_context = context->host->get_tx_context(context); + const size_t output_size = 20; + + uint8_t* output_data = (uint8_t*)calloc(1, output_size); + snprintf((char*)output_data, output_size, "%u", (unsigned)tx_context.block_number); + ret.status_code = EVMC_SUCCESS; + ret.gas_left = msg->gas / 2; + ret.output_data = output_data; + ret.output_size = output_size; + ret.release = &free_result_output_data; + return ret; + } ret.status_code = EVMC_FAILURE; ret.gas_left = 0;