Skip to content
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

cpp: Extend API for creating results #333

Merged
merged 8 commits into from
Aug 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
new `evmc_load_and_configure()` function.
- Added: [[#327](https://github.com/ethereum/evmc/pull/327)]
Full support for 32-bit architectures has been added.
- Added: [[#333](https://github.com/ethereum/evmc/pull/333)]
The C/C++ API for creating execution results in VMs has been extended
to handle common usage cases.
- Added: [[#341](https://github.com/ethereum/evmc/pull/341)]
Support for moving `evmc::vm` objects in C++ API.
- Added: [[#357](https://github.com/ethereum/evmc/pull/357)]
Expand Down
7 changes: 0 additions & 7 deletions bindings/go/evmc/host.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@

#include <stdlib.h>


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


/* Go does not support exporting functions with parameters with const modifiers,
* so we have to cast function pointers to the function types defined in EVMC.
* This disables any type checking of exported Go functions. To mitigate this
Expand Down
17 changes: 5 additions & 12 deletions bindings/go/evmc/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ package evmc
#cgo CFLAGS: -I${SRCDIR}/.. -Wall -Wextra -Wno-unused-parameter

#include <evmc/evmc.h>
#include <evmc/helpers.h>

struct extended_context
{
struct evmc_context context;
int64_t index;
};

void evmc_go_free_result_output(const struct evmc_result* result);

*/
import "C"
import (
Expand Down Expand Up @@ -224,18 +223,12 @@ func call(pCtx unsafe.Pointer, msg *C.struct_evmc_message) C.struct_evmc_result
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)

outputData := (*C.uint8_t)(nil)
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)
outputData = (*C.uint8_t)(&output[0])
}

result := C.evmc_make_result(statusCode, C.int64_t(gasLeft), outputData, C.size_t(len(output)))
result.create_address = evmcAddress(createAddr)
return result
}
27 changes: 25 additions & 2 deletions bindings/go/evmc/host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func (host *testHostContext) EmitLog(addr common.Address, topics []common.Hash,
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
output = []byte("output from testHostContext.Call()")
return output, gas, common.Address{}, nil
}

func TestGetTxContext(t *testing.T) {
Expand All @@ -82,7 +83,29 @@ func TestGetTxContext(t *testing.T) {
t.Errorf("execution unexpected output: %s", output)
}
if gasLeft != 50 {
t.Errorf("execution gas left is incorrect: %x", gasLeft)
t.Errorf("execution gas left is incorrect: %d", gasLeft)
}
if err != nil {
t.Error("execution returned unexpected error")
}
}

func TestCall(t *testing.T) {
vm, _ := Load(modulePath)
defer vm.Destroy()

host := &testHostContext{}
code := []byte("\x60\x00\x80\x80\x80\x80\x80\x80\xf1")

addr := common.Address{}
h := common.Hash{}
output, gasLeft, err := vm.Execute(host, Byzantium, Call, false, 1, 100, addr, addr, nil, h, code, h)

if bytes.Compare(output, []byte("output from testHostContext.Call()")) != 0 {
t.Errorf("execution unexpected output: %s", output)
}
if gasLeft != 99 {
t.Errorf("execution gas left is incorrect: %d", gasLeft)
}
if err != nil {
t.Error("execution returned unexpected error")
Expand Down
10 changes: 1 addition & 9 deletions examples/example_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,7 @@ class ExampleHost : public evmc::Host

evmc::result call(const evmc_message& msg) noexcept final
{
// TODO: Improve C++ API for result creation.
evmc_result res{};
res.status_code = EVMC_REVERT;
auto output = new uint8_t[msg.input_size];
std::copy(&msg.input_data[0], &msg.input_data[msg.input_size], output);
res.output_size = msg.input_size;
res.output_data = output;
res.release = [](const evmc_result* r) noexcept { delete[] r->output_data; };
return evmc::result{res};
return {EVMC_REVERT, msg.gas, msg.input_data, msg.input_size};
}

evmc_tx_context get_tx_context() noexcept final { return {}; }
Expand Down
14 changes: 14 additions & 0 deletions examples/example_vm/example_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ static struct evmc_result execute(struct evmc_instance* instance,
// Assembly: `{ mstore(0, number()) return(0, msize()) }`
const char return_block_number[] = "\x43\x60\x00\x52\x59\x60\x00\xf3";

// Assembly: PUSH(0) 6x DUP1 CALL
const char make_a_call[] = "\x60\x00\x80\x80\x80\x80\x80\x80\xf1";

if (msg->kind == EVMC_CREATE)
{
ret.status_code = EVMC_SUCCESS;
Expand Down Expand Up @@ -157,6 +160,17 @@ static struct evmc_result execute(struct evmc_instance* instance,
ret.release = &free_result_output_data;
return ret;
}
else if (code_size == (sizeof(make_a_call) - 1) &&
strncmp((const char*)code, make_a_call, code_size) == 0)
{
struct evmc_message call_msg;
memset(&call_msg, 0, sizeof(call_msg));
call_msg.kind = EVMC_CALL;
call_msg.depth = msg->depth + 1;
call_msg.gas = msg->gas - (msg->gas / 64);
call_msg.sender = msg->destination;
return context->host->call(context, &call_msg);
}

ret.status_code = EVMC_FAILURE;
ret.gas_left = 0;
Expand Down
5 changes: 4 additions & 1 deletion include/evmc/evmc.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ enum evmc_status_code
* For example, the Client tries running a code in the EVM 1.5. If the
* code is not supported there, the execution falls back to the EVM 1.0.
*/
EVMC_REJECTED = -2
EVMC_REJECTED = -2,

/** The VM failed to allocate the amount of memory needed for execution. */
EVMC_OUT_OF_MEMORY = -3
};

/* Forward declaration. */
Expand Down
20 changes: 20 additions & 0 deletions include/evmc/evmc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ constexpr bytes32 operator"" _bytes32() noexcept

using namespace literals;


/// Alias for evmc_make_result().
constexpr auto make_result = evmc_make_result;

/// @copydoc evmc_result
///
/// This is a RAII wrapper for evmc_result and objects of this type
Expand All @@ -268,6 +272,22 @@ class result : private evmc_result
using evmc_result::output_size;
using evmc_result::status_code;

/// Creates the result from the provided arguments.
///
/// The provided output is copied to memory allocated with malloc()
/// and the evmc_result::release function is set to one invoking free().
///
/// @param _status_code The status code.
/// @param _gas_left The amount of gas left.
/// @param _output_data The pointer to the output.
/// @param _output_size The output size.
result(evmc_status_code _status_code,
chfast marked this conversation as resolved.
Show resolved Hide resolved
int64_t _gas_left,
const uint8_t* _output_data,
size_t _output_size) noexcept
: evmc_result{make_result(_status_code, _gas_left, _output_data, _output_size)}
{}

/// Converting constructor from raw evmc_result.
explicit result(evmc_result const& res) noexcept : evmc_result{res} {}

Expand Down
54 changes: 54 additions & 0 deletions include/evmc/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#pragma once

#include <evmc/evmc.h>
#include <stdlib.h>
#include <string.h>

/**
* Returns true if the VM instance has a compatible ABI version.
Expand Down Expand Up @@ -107,6 +109,58 @@ static inline struct evmc_result evmc_execute(struct evmc_instance* instance,
return instance->execute(instance, context, rev, msg, code, code_size);
}

/// The evmc_result release function using free() for releasing the memory.
///
/// This function is used in the evmc_make_result(),
/// but may be also used in other case if convenient.
///
/// @param result The result object.
static void evmc_free_result_memory(const struct evmc_result* result)
{
free((uint8_t*)result->output_data);
}

/// Creates the result from the provided arguments.
///
/// The provided output is copied to memory allocated with malloc()
/// and the evmc_result::release function is set to one invoking free().
///
/// In case of memory allocation failure, the result has all fields zeroed
/// and only evmc_result::status_code is set to ::EVMC_OUT_OF_MEMORY internal error.
///
/// @param status_code The status code.
/// @param gas_left The amount of gas left.
/// @param output_data The pointer to the output.
/// @param output_size The output size.
static inline struct evmc_result evmc_make_result(enum evmc_status_code status_code,
int64_t gas_left,
const uint8_t* output_data,
size_t output_size)
{
struct evmc_result result;
memset(&result, 0, sizeof(result));

if (output_size != 0)
{
uint8_t* buffer = (uint8_t*)malloc(output_size);

if (!buffer)
{
result.status_code = EVMC_OUT_OF_MEMORY;
Copy link
Member

@axic axic Jul 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not convinced this it the right way to do it, given it confuses status of the execution vs. helpers, but if OOM is hit there is a bigger underlying issue. Perhaps worth highlighting this in the description above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if OOM is hit there is a bigger underlying issue

What?

I don't thing this is the best way of passing output buffer. This will rather be only useful for Go, so we can drop this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if OOM is hit there is a bigger underlying issue

What?

OOM = out of memory

Basically I'm ok with this change given this new specific error code is introduced.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I had issue with understanding the sentence after "OOM". The OOM itself is difficult to handle in general. E.g. it will never happen on Linux in practice as it will happily try to allocate 2 TB of memory and in the end kill the process.

Anyway, the new specific error code was introduced: https://github.com/ethereum/evmc/pull/333/files#diff-8e9caa43721b131d9f7897e2bfd98299R309.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I know, that's the line I commented on. Just having this documented on make_result would be useful.

return result;
}

memcpy(buffer, output_data, output_size);
result.output_data = buffer;
result.output_size = output_size;
result.release = evmc_free_result_memory;
}

result.status_code = status_code;
result.gas_left = gas_left;
return result;
}

/**
* Releases the resources allocated to the execution result.
*
Expand Down
30 changes: 30 additions & 0 deletions test/unittests/test_cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,33 @@ TEST(cpp, result_move)
}
EXPECT_EQ(release_called, 2);
}

TEST(cpp, result_create_no_output)
{
auto r = evmc::result{EVMC_REVERT, 1, nullptr, 0};
EXPECT_EQ(r.status_code, EVMC_REVERT);
EXPECT_EQ(r.gas_left, 1);
EXPECT_FALSE(r.output_data);
EXPECT_EQ(r.output_size, 0);
}

TEST(cpp, result_create)
{
const uint8_t output[] = {1, 2};
auto r = evmc::result{EVMC_FAILURE, -1, output, sizeof(output)};
EXPECT_EQ(r.status_code, EVMC_FAILURE);
EXPECT_EQ(r.gas_left, -1);
ASSERT_TRUE(r.output_data);
ASSERT_EQ(r.output_size, 2);
EXPECT_EQ(r.output_data[0], 1);
EXPECT_EQ(r.output_data[1], 2);

auto c = evmc::make_result(r.status_code, r.gas_left, r.output_data, r.output_size);
EXPECT_EQ(c.status_code, r.status_code);
EXPECT_EQ(c.gas_left, r.gas_left);
ASSERT_EQ(c.output_size, r.output_size);
EXPECT_EQ(evmc::address{c.create_address}, evmc::address{r.create_address});
ASSERT_TRUE(c.release);
EXPECT_TRUE(std::memcmp(c.output_data, r.output_data, c.output_size) == 0);
c.release(&c);
}