Skip to content

Commit

Permalink
Merge pull request #547 from eosnetworkfoundation/elmato/yield-plus-i…
Browse files Browse the repository at this point in the history
…ntegration

Add read-only transaction execution support to EVM contract
  • Loading branch information
elmato committed May 26, 2023
2 parents d410c3e + 8707365 commit 6d2f2df
Show file tree
Hide file tree
Showing 15 changed files with 503 additions and 2 deletions.
2 changes: 2 additions & 0 deletions contract/include/evm_runtime/evm_contract.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class [[eosio::contract]] evm_contract : public contract
*/
[[eosio::action]] void freeze(bool value);

[[eosio::action]] void exec(const exec_input& input, const std::optional<exec_callback>& callback);

[[eosio::action]] void pushtx(eosio::name miner, const bytes& rlptx);

[[eosio::action]] void open(eosio::name owner);
Expand Down
3 changes: 2 additions & 1 deletion contract/include/evm_runtime/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ struct db_stats {
struct state : State {
name _self;
name _ram_payer;
bool _read_only;
mutable std::map<evmc::address, uint64_t> addr2id;
mutable std::map<bytes32, bytes> addr2code;
mutable db_stats stats;

explicit state(name self, name ram_payer) : _self(self), _ram_payer(ram_payer){}
explicit state(name self, name ram_payer, bool read_only=false) : _self(self), _ram_payer(ram_payer), _read_only{read_only}{}

std::optional<Account> read_account(const evmc::address& address) const noexcept override;

Expand Down
26 changes: 26 additions & 0 deletions contract/include/evm_runtime/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,32 @@ namespace evm_runtime {
evmc::address to_address(const bytes& addr);
evmc::bytes32 to_bytes32(const bytes& data);
uint256 to_uint256(const bytes& value);

struct exec_input {
std::optional<bytes> context;
std::optional<bytes> from;
bytes to;
bytes data;
std::optional<bytes> value;

EOSLIB_SERIALIZE(exec_input, (context)(from)(to)(data)(value));
};

struct exec_callback {
eosio::name contract;
eosio::name action;

EOSLIB_SERIALIZE(exec_callback, (contract)(action));
};

struct exec_output {
int32_t status;
bytes data;
std::optional<bytes> context;

EOSLIB_SERIALIZE(exec_output, (status)(data)(context));
};

} //namespace evm_runtime

namespace eosio {
Expand Down
48 changes: 48 additions & 0 deletions contract/src/actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
#define LOGTIME(MSG)
#endif

extern "C" {
__attribute__((eosio_wasm_import))
void set_action_return_value(void*, size_t);
}

namespace silkworm {
// provide no-op bloom
Bloom logs_bloom(const std::vector<Log>& logs) {
Expand Down Expand Up @@ -323,6 +328,49 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction&
return receipt;
}

void evm_contract::exec(const exec_input& input, const std::optional<exec_callback>& callback) {

assert_unfrozen();

const auto& current_config = _config.get();
std::optional<std::pair<const std::string, const ChainConfig*>> found_chain_config = lookup_known_chain(current_config.chainid);
check( found_chain_config.has_value(), "failed to find expected chain config" );

evm_common::block_mapping bm(current_config.genesis_time.sec_since_epoch());

Block block;
evm_common::prepare_block_header(block.header, bm, get_self().value,
bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count()));

evm_runtime::state state{get_self(), get_self(), true};
IntraBlockState ibstate{state};

EVM evm{block, ibstate, *found_chain_config.value().second};

Transaction txn;
txn.to = to_address(input.to);
txn.data = Bytes{input.data.begin(), input.data.end()};
txn.from = input.from.has_value() ? to_address(input.from.value()) : evmc::address{};
txn.value = input.value.has_value() ? to_uint256(input.value.value()) : 0;

const CallResult vm_res{evm.execute(txn, 0x7ffffffffff)};

exec_output output{
.status = int32_t(vm_res.status),
.data = bytes{vm_res.data.begin(), vm_res.data.end()},
.context = input.context
};

if(callback.has_value()) {
const auto& cb = callback.value();
action(std::vector<permission_level>{}, cb.contract, cb.action, output
).send();
} else {
auto output_bin = eosio::pack(output);
set_action_return_value(output_bin.data(), output_bin.size());
}
}

void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) {
LOGTIME("EVM START");

Expand Down
4 changes: 3 additions & 1 deletion contract/src/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ void state::begin_block(uint64_t block_number) {}

void state::update_account(const evmc::address& address, std::optional<Account> initial,
std::optional<Account> current) {

check(!_read_only, "ro state");
const bool equal{current == initial};
if(equal) return;

Expand Down Expand Up @@ -182,6 +182,7 @@ bool state::gc(uint32_t max) {
}

void state::update_account_code(const evmc::address& address, uint64_t, const evmc::bytes32& code_hash, ByteView code) {
check(!_read_only, "ro state");
account_code_table codes(_self, _self.value);
auto inxc = codes.get_index<"by.codehash"_n>();
auto itrc = inxc.find(make_key(code_hash));
Expand Down Expand Up @@ -225,6 +226,7 @@ void state::update_account_code(const evmc::address& address, uint64_t, const ev
void state::update_storage(const evmc::address& address, uint64_t incarnation, const evmc::bytes32& location,
const evmc::bytes32& initial, const evmc::bytes32& current) {

check(!_read_only, "ro state");
account_table accounts(_self, _self.value);
auto inx = accounts.get_index<"by.address"_n>();
auto itr = inx.find(make_key(address));
Expand Down
1 change: 1 addition & 0 deletions contract/src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ bytes to_bytes(const evmc::address& addr) {

evmc::address to_address(const bytes& addr) {
evmc::address res;
eosio::check(addr.size() == 20, "wrong length");
memcpy(res.bytes, addr.data(), 20);
return res;
}
Expand Down
1 change: 1 addition & 0 deletions contract/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_eosio_test_executable( unit_test
${CMAKE_SOURCE_DIR}/mapping_tests.cpp
${CMAKE_SOURCE_DIR}/gas_fee_tests.cpp
${CMAKE_SOURCE_DIR}/blockhash_tests.cpp
${CMAKE_SOURCE_DIR}/exec_tests.cpp
${CMAKE_SOURCE_DIR}/main.cpp
${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/encode.cpp
${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/decode.cpp
Expand Down
43 changes: 43 additions & 0 deletions contract/tests/basic_evm_tester.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "basic_evm_tester.hpp"
#include <fc/io/raw.hpp>

namespace fc {

Expand Down Expand Up @@ -166,6 +167,44 @@ transaction_trace_ptr basic_evm_tester::transfer_token(name from, name to, asset
token_account_name, "transfer"_n, from, mvo()("from", from)("to", to)("quantity", quantity)("memo", memo));
}

action basic_evm_tester::get_action( account_name code, action_name acttype, vector<permission_level> auths,
const bytes& data )const { try {
const auto& acnt = control->get_account(code);
auto abi = acnt.get_abi();
chain::abi_serializer abis(abi, abi_serializer::create_yield_function( abi_serializer_max_time ));

string action_type_name = abis.get_action_type(acttype);
FC_ASSERT( action_type_name != string(), "unknown action type ${a}", ("a",acttype) );

action act;
act.account = code;
act.name = acttype;
act.authorization = auths;
act.data = data;
return act;
} FC_CAPTURE_AND_RETHROW() }

transaction_trace_ptr basic_evm_tester::push_action( const account_name& code,
const action_name& acttype,
const account_name& actor,
const bytes& data,
uint32_t expiration,
uint32_t delay_sec)
{
vector<permission_level> auths;
auths.push_back( permission_level{actor, config::active_name} );
try {
signed_transaction trx;
trx.actions.emplace_back( get_action( code, acttype, auths, data ) );
set_transaction_headers( trx, expiration, delay_sec );
for (const auto& auth : auths) {
trx.sign( get_private_key( auth.actor, auth.permission.to_string() ), control->get_chain_id() );
}

return push_transaction( trx );
} FC_CAPTURE_AND_RETHROW( (code)(acttype)(auths)(data)(expiration)(delay_sec) ) }


void basic_evm_tester::init(const uint64_t chainid,
const uint64_t gas_price,
const uint32_t miner_cut,
Expand Down Expand Up @@ -242,6 +281,10 @@ basic_evm_tester::generate_tx(const evmc::address& to, const intx::uint256& valu
.value = value,
};
}
transaction_trace_ptr basic_evm_tester::exec(const exec_input& input, const std::optional<exec_callback>& callback) {
auto binary_data = fc::raw::pack<exec_input, std::optional<exec_callback>>(input, callback);
return basic_evm_tester::push_action(evm_account_name, "exec"_n, evm_account_name, bytes{binary_data.begin(), binary_data.end()});
}

void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner)
{
Expand Down
36 changes: 36 additions & 0 deletions contract/tests/basic_evm_tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,25 @@ struct fee_parameters
std::optional<asset> ingress_bridge_fee;
};

struct exec_input {
std::optional<bytes> context;
std::optional<bytes> from;
bytes to;
bytes data;
std::optional<bytes> value;
};

struct exec_callback {
name contract;
name action;
};

struct exec_output {
int32_t status;
bytes data;
std::optional<bytes> context;
};

} // namespace evm_test


Expand All @@ -100,6 +119,10 @@ FC_REFLECT(evm_test::account_object, (id)(address)(nonce)(balance))
FC_REFLECT(evm_test::storage_slot, (id)(key)(value))
FC_REFLECT(evm_test::fee_parameters, (gas_price)(miner_cut)(ingress_bridge_fee))

FC_REFLECT(evm_test::exec_input, (context)(from)(to)(data)(value))
FC_REFLECT(evm_test::exec_callback, (contract)(action))
FC_REFLECT(evm_test::exec_output, (status)(data)(context))

namespace evm_test {
class evm_eoa
{
Expand All @@ -126,6 +149,8 @@ class evm_eoa
class basic_evm_tester : public testing::validating_tester
{
public:
using testing::validating_tester::push_action;

static constexpr name token_account_name = "eosio.token"_n;
static constexpr name faucet_account_name = "faucet"_n;
static constexpr name evm_account_name = "evm"_n;
Expand All @@ -147,6 +172,16 @@ class basic_evm_tester : public testing::validating_tester

transaction_trace_ptr transfer_token(name from, name to, asset quantity, std::string memo = "");

action get_action( account_name code, action_name acttype, vector<permission_level> auths,
const bytes& data )const;

transaction_trace_ptr push_action( const account_name& code,
const action_name& acttype,
const account_name& actor,
const bytes& data,
uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
uint32_t delay_sec = 0 );

void init(const uint64_t chainid = evm_chain_id,
const uint64_t gas_price = suggested_gas_price,
const uint32_t miner_cut = suggested_miner_cut,
Expand All @@ -162,6 +197,7 @@ class basic_evm_tester : public testing::validating_tester
silkworm::Transaction
generate_tx(const evmc::address& to, const intx::uint256& value, uint64_t gas_limit = 21000) const;

transaction_trace_ptr exec(const exec_input& input, const std::optional<exec_callback>& callback);
void pushtx(const silkworm::Transaction& trx, name miner = evm_account_name);
evmc::address deploy_contract(evm_eoa& eoa, evmc::bytes bytecode);

Expand Down
3 changes: 3 additions & 0 deletions contract/tests/contracts.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ struct contracts {
static std::vector<uint8_t> evm_runtime_wasm() { return read_wasm("${CMAKE_CURRENT_SOURCE_DIR}/../build/evm_runtime/evm_runtime.wasm"); }
static std::vector<char> evm_runtime_abi() { return read_abi("${CMAKE_CURRENT_SOURCE_DIR}/../build/evm_runtime/evm_runtime.abi"); }

static std::vector<uint8_t> evm_read_callback_wasm() { return read_wasm("${CMAKE_CURRENT_SOURCE_DIR}/contracts/evm_read_callback/evm_read_callback.wasm"); }
static std::vector<char> evm_read_callback_abi() { return read_abi("${CMAKE_CURRENT_SOURCE_DIR}/contracts/evm_read_callback/evm_read_callback.abi"); }

static std::string eth_test_folder() {
return "${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/third_party/tests";
}
Expand Down
46 changes: 46 additions & 0 deletions contract/tests/contracts/evm_read_callback/evm_read_callback.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"____comment": "This file was generated with eosio-abigen. DO NOT EDIT ",
"version": "eosio::abi/1.2",
"types": [],
"structs": [
{
"name": "callback",
"base": "",
"fields": [
{
"name": "output",
"type": "exec_output"
}
]
},
{
"name": "exec_output",
"base": "",
"fields": [
{
"name": "status",
"type": "int32"
},
{
"name": "data",
"type": "bytes"
},
{
"name": "context",
"type": "bytes?"
}
]
}
],
"actions": [
{
"name": "callback",
"type": "callback",
"ricardian_contract": ""
}
],
"tables": [],
"ricardian_clauses": [],
"variants": [],
"action_results": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "evm_read_callback.hpp"
ACTION evm_read_callback::callback( const exec_output& output ) {
output_table outputs(get_self(), get_self().value);
outputs.emplace(get_self(), [&](auto& row){
row.id = outputs.available_primary_key();
row.output = output;
});
}
33 changes: 33 additions & 0 deletions contract/tests/contracts/evm_read_callback/evm_read_callback.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <eosio/eosio.hpp>
using namespace eosio;

typedef std::vector<uint8_t> bytes;

struct exec_output {
int32_t status;
bytes data;
std::optional<bytes> context;

EOSLIB_SERIALIZE(exec_output, (status)(data)(context));
};

struct exec_output_row {
uint64_t id;
exec_output output;

uint64_t primary_key()const {
return id;
}


EOSLIB_SERIALIZE(exec_output_row, (id)(output));
};

typedef multi_index<"output"_n, exec_output_row> output_table;

CONTRACT evm_read_callback : public contract {
public:
using contract::contract;

ACTION callback( const exec_output& output );
};
Binary file not shown.

0 comments on commit 6d2f2df

Please sign in to comment.