-
Notifications
You must be signed in to change notification settings - Fork 81
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
EVM-C v3 #112
Changes from all commits
7c3e9f7
907fe26
b8c914b
6a3c986
aae2f70
68b7a49
f93d608
f2e7dec
512f149
3ee1bdd
f2f574f
bafcab5
5cc6f6b
ffc86a1
1d92d63
1084d94
084db80
e688f36
946506f
26b2b97
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 |
---|---|---|
|
@@ -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; | ||
|
@@ -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) { | ||
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: | ||
|
@@ -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, | ||
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 is there 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. 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... 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. But that means two 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. 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. 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. Do we have any recommendations how the VMs should ensure that unauthorised 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. VM should use the address from a message in this case, unless it wants to implement internal calls by itself... 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. 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. 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. 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) | ||
{ | ||
|
@@ -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) { | ||
|
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; | ||
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 is the block hash not part of the state? Because it is being offloaded to a contract? 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. No. BLOCKHASH is not query about the state exactly and it does not match the Metropolis changes are no going to help because we are not going to remove (or deprecate) BLOCKHASH opcode. However, EVM implementation could actually 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. Sounds reasonable. (Note, the opcode will actually be a call to the contract.) |
||
|
||
int example_option; | ||
}; | ||
|
||
static void evm_destroy(struct evm_instance* evm) | ||
|
@@ -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; | ||
} | ||
|
||
|
@@ -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; | ||
|
@@ -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,}}; | ||
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; | ||
|
@@ -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; | ||
} | ||
|
||
|
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.
Why is it better not to have this union?
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.
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.