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

Initial support for runevm contract #513

Merged
merged 1 commit into from
May 2, 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,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]

## Interfaces

Expand Down Expand Up @@ -129,3 +130,4 @@ Apache 2.0
[Sentinel system contract]: https://github.com/ewasm/design/blob/master/system_contracts.md#sentinel-contract
[EVM Transcompiler]: https://github.com/ewasm/design/blob/master/system_contracts.md#evm-transcompiler
[EEI]: https://github.com/ewasm/design/blob/master/eth_interface.md
[runevm]: https://github.com/axic/runevm
113 changes: 99 additions & 14 deletions src/hera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ enum class hera_evm1mode {
reject,
fallback,
evm2wasm_contract,
runevm_contract,
};

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 },
};

using WasmEngineCreateFn = unique_ptr<WasmEngine>(*)();
Expand All @@ -67,25 +75,21 @@ 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
Copy link
Member

Choose a reason for hiding this comment

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

@chfast hmm, why did we had the static create() if we only ever used the default constructor?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't remember anything about it. If the default constructor works for this case it's good choice.

#elif HERA_WABT
new WabtEngine
WabtEngine::create
#elif HERA_WAVM
new WavmEngine
WavmEngine::create
#else
#error "No engine requested."
#endif
};
;

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,39 @@ 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();
// TODO: should we catch exceptions here?
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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (status == EVMC_SUCCESS && result.returnValue.size() > 0)
if (status == EVMC_SUCCESS && !result.returnValue.empty())

but the returnValue is not really needed.

ret = move(result.returnValue);

return {status, move(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 +227,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(
status == EVMC_SUCCESS,
ContractValidationFailure,
"runevm has failed."
);
ensureCondition(
ret.size() > 0,
ContractValidationFailure,
"Runevm returned empty."
);
ensureCondition(
hasWasmPreamble(ret),
ContractValidationFailure,
"Runevm result has no wasm preamble."
);

return ret;
}

void hera_destroy_result(evmc_result const* result) noexcept
{
delete[] result->output_data;
Expand Down Expand Up @@ -247,6 +324,12 @@ 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");
axic marked this conversation as resolved.
Show resolved Hide resolved
// Runevm does interface metering on its own
meterInterfaceGas = false;
break;
}
}

Expand Down Expand Up @@ -367,7 +450,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 +509,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();
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, you were correct, .execute() is stateless at the moment. Would you mind if I remove the changes regarding the engines and merge this PR?

And then could you create a new PR changing the handling of the engine, because that might be a bug currently, but want to have @chfast's opinion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, feel free to modify the PR.

return EVMC_SET_OPTION_SUCCESS;
}
return EVMC_SET_OPTION_INVALID_VALUE;
Expand Down