Skip to content

Commit

Permalink
Add initial support for runevm system contract
Browse files Browse the repository at this point in the history
  • Loading branch information
s1na committed Apr 30, 2019
1 parent acba3a9 commit 88244c1
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 15 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ These are to be used via EVMC `set_option`:

- `engine=<engine>` will select the underlying WebAssembly engine, where the only accepted values currently are `binaryen`, `wabt`, and `wavm`
- `metering=true` will enable metering of bytecode at deployment using the [Sentinel system contract] (set to `false` by default)
- `disable-interface-metering=true` will disable metering of EEI methods (set to `false` by default)
- `benchmark=true` will produce execution timings and output it to both standard error output and `hera_benchmarks.log` file.
- `evm1mode=<evm1mode>` will select how EVM1 bytecode is handled
- `sys:<alias/address>=file.wasm` will override the code executing at the specified address with code loaded from a filepath at runtime. This option supports aliases for system contracts as well, such that `sys:sentinel=file.wasm` and `sys:evm2wasm=file.wasm` are both valid. **This option is intended for debugging purposes.**
Expand All @@ -75,6 +76,7 @@ These are to be used via EVMC `set_option`:
- `reject` will reject any EVM1 bytecode with an error (the default setting)
- `fallback` will allow EVM1 bytecode to be passed through to the client for execution
- `evm2wasm` will enable transformation of bytecode using the [EVM Transcompiler]
- `runevm` will transform EVM1 bytecode using [runevm](https://github.com/axic/runevm)

## Interfaces

Expand Down
111 changes: 96 additions & 15 deletions src/hera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum class hera_evm1mode {
reject,
fallback,
evm2wasm_contract,
runevm_contract,
};

using WasmEngineCreateFn = unique_ptr<WasmEngine>(*)();
Expand All @@ -67,25 +68,28 @@ const map<string, WasmEngineCreateFn> wasm_engine_map {
#endif
};

const map<string, hera_evm1mode> evm1mode_options {
{ "reject", hera_evm1mode::reject },
{ "fallback", hera_evm1mode::fallback },
{ "evm2wasm", hera_evm1mode::evm2wasm_contract },
};

struct hera_instance : evmc_instance {
unique_ptr<WasmEngine> engine{
WasmEngineCreateFn wasmEngineCreateFn =
// This is the order of preference.
#if HERA_BINARYEN
new BinaryenEngine
BinaryenEngine::create
#elif HERA_WABT
new WabtEngine
WabtEngine::create
#elif HERA_WAVM
new WavmEngine
WavmEngine::create
#else
#error "No engine requested."
#endif
};
;

const map<string, hera_evm1mode> evm1mode_options {
{ "reject", hera_evm1mode::reject },
{ "fallback", hera_evm1mode::fallback },
{ "evm2wasm", hera_evm1mode::evm2wasm_contract },
{ "runevm", hera_evm1mode::runevm_contract },
};

struct hera_instance : evmc_instance {
unique_ptr<WasmEngine> engine = wasmEngineCreateFn();
hera_evm1mode evm1mode = hera_evm1mode::reject;
bool metering = false;
map<evmc_address, bytes> contract_preload_list;
Expand All @@ -95,6 +99,7 @@ struct hera_instance : evmc_instance {

const evmc_address sentinelAddress = { .bytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa } };
const evmc_address evm2wasmAddress = { .bytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xb } };
const evmc_address runevmAddress = { .bytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc } };

// Calls a system contract at @address with input data @input.
// It is a "staticcall" with sender 000...000 and no value.
Expand Down Expand Up @@ -132,6 +137,38 @@ pair<evmc_status_code, bytes> callSystemContract(
return {result.status_code, ret};
}

pair<evmc_status_code, bytes> locallyExecuteSystemContract(
evmc_context* context,
evmc_address const& address,
int64_t & gas,
bytes_view input,
bytes_view code,
bytes_view state_code
) {
const evmc_message message = {
.kind = EVMC_CALL,
.flags = EVMC_STATIC,
.depth = 0,
.gas = gas,
.destination = address,
.sender = {},
.input_data = input.data(),
.input_size = input.size(),
.value = {},
.create2_salt = {},
};

unique_ptr<WasmEngine> engine = wasmEngineCreateFn();
ExecutionResult result = engine->execute(context, code, state_code, message, false);

bytes ret;
evmc_status_code status = result.isRevert ? EVMC_REVERT : EVMC_SUCCESS;
if (status == EVMC_SUCCESS && result.returnValue.size() > 0)
ret = move(result.returnValue);

return {status, ret};
}

// Calls the Sentinel contract with input data @input.
// @returns the validated and metered output or empty output otherwise.
bytes sentinel(evmc_context* context, bytes_view input)
Expand Down Expand Up @@ -189,6 +226,45 @@ bytes evm2wasm(evmc_context* context, bytes_view input) {
return ret;
}

// Calls the runevm contract.
// @returns a wasm-based evm interpreter.
bytes runevm(evmc_context* context, bytes code) {
HERA_DEBUG << "Calling runevm (code " << code.size() << " bytes)...\n";

int64_t gas = numeric_limits<int64_t>::max(); // do not charge for metering yet (give unlimited gas)
evmc_status_code status;
bytes ret;

tie(status, ret) = locallyExecuteSystemContract(
context,
runevmAddress,
gas,
{},
code,
code
);

HERA_DEBUG << "runevm done (output " << ret.size() << " bytes) with status=" << status << "\n";

ensureCondition(
ret.size() > 0,
ContractValidationFailure,
"Runevm returned empty."
);
ensureCondition(
hasWasmPreamble(ret),
ContractValidationFailure,
"Runevm result has no wasm preamble."
);
ensureCondition(
status == EVMC_SUCCESS,
ContractValidationFailure,
"runevm has failed."
);

return ret;
}

void hera_destroy_result(evmc_result const* result) noexcept
{
delete[] result->output_data;
Expand Down Expand Up @@ -247,6 +323,10 @@ evmc_result hera_execute(
HERA_DEBUG << "Non-WebAssembly input, failure.\n";
ret.status_code = EVMC_FAILURE;
return ret;
case hera_evm1mode::runevm_contract:
run_code = runevm(context, hera->contract_preload_list[runevmAddress]);
ensureCondition(run_code.size() > 8, ContractValidationFailure, "Interpreting via runevm failed");
meterInterfaceGas = false;
}
}

Expand Down Expand Up @@ -305,7 +385,6 @@ evmc_result hera_execute(
ret.output_data = output_data;
ret.release = hera_destroy_result;
}

ret.status_code = result.isRevert ? EVMC_REVERT : EVMC_SUCCESS;
ret.gas_left = result.gasLeft;
} catch (EndExecution const&) {
Expand Down Expand Up @@ -367,7 +446,8 @@ bool hera_parse_sys_option(hera_instance *hera, string const& _name, string cons
// alias
const map<string, evmc_address> aliases = {
{ string("sentinel"), sentinelAddress },
{ string("evm2wasm"), evm2wasmAddress }
{ string("evm2wasm"), evm2wasmAddress },
{ string("runevm"), runevmAddress },
};

if (aliases.count(name) == 0) {
Expand Down Expand Up @@ -425,7 +505,8 @@ evmc_set_option_result hera_set_option(
if (strcmp(name, "engine") == 0) {
auto it = wasm_engine_map.find(value);
if (it != wasm_engine_map.end()) {
hera->engine = it->second();
wasmEngineCreateFn = it->second;
hera->engine = wasmEngineCreateFn();
return EVMC_SET_OPTION_SUCCESS;
}
return EVMC_SET_OPTION_INVALID_VALUE;
Expand Down

0 comments on commit 88244c1

Please sign in to comment.