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

EVM-C v3 #112

Merged
merged 20 commits into from
May 10, 2017
Merged
Show file tree
Hide file tree
Changes from 17 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
52 changes: 33 additions & 19 deletions examples/capi.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
#include "evm.h"


struct evm_uint256be balance(struct evm_env* env, struct evm_uint160be address)
struct evm_uint256be balance(struct evm_env* env,
const struct evm_uint160be* address)
{
struct evm_uint256be ret = {.bytes = {1, 2, 3, 4}};
return ret;
Expand All @@ -19,19 +20,21 @@ struct evm_uint160be address(struct evm_env* env)
static void query(union evm_variant* result,
struct evm_env* env,
enum evm_query_key key,
const union evm_variant* arg) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is it better not to have this union?

Copy link
Member Author

Choose a reason for hiding this comment

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

Previously this function was used to get other data (e.g. EXTCODESIZE -- the argument was the address, now address is explicit argument, BLOCKHASH -- the argument was int64 number of the block). Now the only possible argument is the storage key for SLOAD, so the type has changed from evm_variant to evm_uint256be.

const struct evm_uint160be* address,
const struct evm_uint256be* storage_key) {
printf("EVM-C: QUERY %d\n", key);
switch (key) {
case EVM_GAS_LIMIT:
result->int64 = 314;
case EVM_CODE_BY_ADDRESS:
result->data = NULL;
result->data_size = 0;
break;

case EVM_BALANCE:
result->uint256be = balance(env, arg->address);
result->uint256be = balance(env, address);
break;

case EVM_ADDRESS:
result->address = address(env);
case EVM_ACCOUNT_EXISTS:
result->int64 = 0;
break;

default:
Expand All @@ -41,6 +44,7 @@ static void query(union evm_variant* result,

static void update(struct evm_env* env,
enum evm_update_key key,
const struct evm_uint160be* addr,
Copy link
Member

Choose a reason for hiding this comment

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

Why is there addr and arg1, arg2?

Copy link
Member Author

Choose a reason for hiding this comment

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

So update callback supports SSTORE (arg1: key, arg2: value), LOG (arg1: topics, arg2: data) and SELFDESTRUCT (arg1: address). See example implementation in cpp-ethereum: https://github.com/ethereum/cpp-ethereum/blob/4cb9b1085a11a04d5e6f6429df3681eaab2480fd/libevm/JitVM.cpp#L92-L112.

Because logs are not state changes per se, we can create another callback function for logs only (I forgot I wasn't happy about this). But in the end SSTORE and SELFDESTRUCT needs different types of arguments so evm_variant will be needed...

Copy link
Member

Choose a reason for hiding this comment

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

But that means two evm_variant args should be enough?

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. For SSTORE you have to provide address, key and value because the address of the currently executed account is only in the message. The "host" application does not need to remember what account EVM is going to change. This is abstraction what is introduced with message / tx data / state separation. Go and Python have internal API that reflects this already.

Copy link
Member

Choose a reason for hiding this comment

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

Do we have any recommendations how the VMs should ensure that unauthorised SSTORE doesn't happen?

Copy link
Member Author

Choose a reason for hiding this comment

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

VM should use the address from a message in this case, unless it wants to implement internal calls by itself...

Copy link
Member

Choose a reason for hiding this comment

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

Maybe in that case we should also pass the current call context? Talking a bit blindly here, as haven't done the client side yet.

Copy link
Member Author

Choose a reason for hiding this comment

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

All we need (at the moment) to manipulate or query the state (read: the map of all accounts) it an account address. All other data in a message (call context) is volatile and has not use for the state, i.e. value, input data, gas limit, etc.

const union evm_variant* arg1,
const union evm_variant* arg2)
{
Expand All @@ -49,38 +53,48 @@ static void update(struct evm_env* env,

static int64_t call(
struct evm_env* _opaqueEnv,
enum evm_call_kind _kind,
int64_t _gas,
const struct evm_uint160be* _address,
const struct evm_uint256be* _value,
uint8_t const* _inputData,
size_t _inputSize,
const struct evm_message* _msg,
uint8_t* _outputData,
size_t _outputSize
)
{
printf("EVM-C: CALL %d\n", _kind);
printf("EVM-C: CALL (depth: %d)\n", _msg->depth);
return EVM_CALL_FAILURE;
}

static void get_tx_context(struct evm_tx_context* result, struct evm_env* env)
{

}

static void get_block_hash(struct evm_uint256be* result, struct evm_env* env,
int64_t number)
{

}

/// Example how the API is supposed to be used.
int main(int argc, char *argv[]) {
struct evm_factory factory = examplevm_get_factory();
if (factory.abi_version != EVM_ABI_VERSION)
return 1; // Incompatible ABI version.

struct evm_instance* jit = factory.create(query, update, call);
struct evm_instance* jit = factory.create(query, update, call,
get_tx_context, get_block_hash);

uint8_t const code[] = "Place some EVM bytecode here";
const size_t code_size = sizeof(code);
struct evm_uint256be code_hash = {.bytes = {1, 2, 3,}};
uint8_t const input[] = "Hello World!";
struct evm_uint256be value = {{1, 0, 0, 0}};

struct evm_uint256be value = {{1, 0,}};
struct evm_uint160be addr = {{0, 1, 2,}};
int64_t gas = 200000;

struct evm_message msg = {addr, addr, value, input, sizeof(input),
code_hash, gas, 0};

struct evm_result result =
jit->execute(jit, NULL, EVM_HOMESTEAD, code_hash, code, code_size, gas,
input, sizeof(input), value);
jit->execute(jit, NULL, EVM_HOMESTEAD, &msg, code, code_size);

printf("Execution result:\n");
if (result.code != EVM_SUCCESS) {
Expand Down
84 changes: 55 additions & 29 deletions examples/examplevm.c
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "evm.h"


struct examplevm
{
struct evm_instance instance;
evm_query_fn query_fn;
evm_update_fn update_fn;
evm_query_state_fn query_fn;
evm_update_state_fn update_fn;
evm_call_fn call_fn;
evm_get_tx_context_fn get_tx_context_fn;
evm_get_block_hash_fn get_block_hash_fn;
Copy link
Member

Choose a reason for hiding this comment

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

Why is the block hash not part of the state? Because it is being offloaded to a contract?

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. BLOCKHASH is not query about the state exactly and it does not match the query() function callback where you always have to provide the address of an account you query about. In the end it is not transaction data, nor query about accounts... so it got its own callback function.

Metropolis changes are no going to help because we are not going to remove (or deprecate) BLOCKHASH opcode. However, EVM implementation could actually use query() function to get needed information, instead of relying on dedicated callback function.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds reasonable.

(Note, the opcode will actually be a call to the contract.)


int example_option;
};

static void evm_destroy(struct evm_instance* evm)
Expand All @@ -19,12 +24,19 @@ static void evm_destroy(struct evm_instance* evm)
/// Example options.
///
/// VMs are allowed to omit this function implementation.
int evm_set_option(struct evm_instance* evm,
int evm_set_option(struct evm_instance* instance,
char const* name,
char const* value)
{
if (strcmp(name, "example-option") == 0)
struct examplevm* vm = (struct examplevm*)instance;
if (strcmp(name, "example-option") == 0) {
long int v = strtol(value, NULL, 0);
if (v > INT_MAX || v < INT_MIN)
return 0;
vm->example_option = (int)v;
return 1;
}

return 0;
}

Expand All @@ -37,24 +49,20 @@ static void free_result_output_data(struct evm_result const* result)
free((uint8_t*)result->output_data);
}

static struct evm_result evm_execute(struct evm_instance* instance,
struct evm_env* env,
enum evm_mode mode,
struct evm_uint256be code_hash,
uint8_t const* code,
size_t code_size,
int64_t gas,
uint8_t const* input,
size_t input_size,
struct evm_uint256be value)
static struct evm_result execute(struct evm_instance* instance,
struct evm_env* env,
enum evm_mode mode,
const struct evm_message* msg,
const uint8_t* code,
size_t code_size)
{
struct evm_result ret = {};
if (code_size == 0) {
// In case of empty code return a fancy error message.
const char* msg = mode == EVM_METROPOLIS ?
"Welcome to Metropolis!" : "Hello Ethereum!";
ret.output_data = (const uint8_t*)msg;
ret.output_size = strlen(msg);
const char* error = mode == EVM_METROPOLIS ?
"Welcome to Metropolis!" : "Hello Ethereum!";
ret.output_data = (const uint8_t*)error;
ret.output_size = strlen(error);
ret.code = EVM_FAILURE;
ret.release = NULL; // We don't need to release the constant messages.
return ret;
Expand All @@ -66,26 +74,40 @@ static struct evm_result evm_execute(struct evm_instance* instance,
// Solidity inline assembly is used in the examples instead of EVM bytecode.

// Assembly: `{ mstore(0, address()) return(0, msize()) }`.
const char return_by_address[] = "30600052596000f3";
if (code_size == strlen(return_by_address) &&
strncmp((const char*)code, return_by_address, code_size)) {
union evm_variant query_result;
vm->query_fn(&query_result, env, EVM_ADDRESS, NULL);
static const size_t address_size = sizeof(query_result.address);
const char return_address[] = "30600052596000f3";

// Assembly: `{ sstore(0, add(sload(0), 1)) }`
const char counter[] = "600160005401600055";

if (code_size == strlen(return_address) &&
strncmp((const char*)code, return_address, code_size)) {
static const size_t address_size = sizeof(msg->address);
uint8_t* output_data = (uint8_t*)malloc(address_size);
if (!output_data) {
// malloc failed, report internal error.
ret.code = EVM_INTERNAL_ERROR;
return ret;
}
memcpy(output_data, &query_result.address, address_size);
memcpy(output_data, &msg->address, address_size);
ret.code = EVM_SUCCESS;
ret.output_data = output_data;
ret.output_size = address_size;
ret.release = &free_result_output_data;
ret.context = NULL; // We don't need another pointer.
return ret;
}
else if (code_size == strlen(counter) &&
strncmp((const char*)code, counter, code_size)) {
union evm_variant value;
const struct evm_uint256be index = {0, 0, 0, 0};
vm->query_fn(&value, env, EVM_SLOAD, &msg->address, &index);
value.uint256be.bytes[31] += 1;
union evm_variant arg;
arg.uint256be = index;
vm->update_fn(env, EVM_SSTORE, &msg->address, &arg, &value);
ret.code = EVM_SUCCESS;
return ret;
}

ret.release = evm_release_result;
ret.code = EVM_FAILURE;
Expand All @@ -94,18 +116,22 @@ static struct evm_result evm_execute(struct evm_instance* instance,
return ret;
}

static struct evm_instance* evm_create(evm_query_fn query_fn,
evm_update_fn update_fn,
evm_call_fn call_fn)
static struct evm_instance* evm_create(evm_query_state_fn query_fn,
evm_update_state_fn update_fn,
evm_call_fn call_fn,
evm_get_tx_context_fn get_tx_context_fn,
evm_get_block_hash_fn get_block_hash_fn)
{
struct examplevm* vm = calloc(1, sizeof(struct examplevm));
struct evm_instance* interface = &vm->instance;
interface->destroy = evm_destroy;
interface->execute = evm_execute;
interface->execute = execute;
interface->set_option = evm_set_option;
vm->query_fn = query_fn;
vm->update_fn = update_fn;
vm->call_fn = call_fn;
vm->get_tx_context_fn = get_tx_context_fn;
vm->get_block_hash_fn = get_block_hash_fn;
return interface;
}

Expand Down
Loading