From a3e178b0c6b4f1cecf396bec54eca706326981a9 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 27 Dec 2023 15:42:34 -0300 Subject: [PATCH 01/23] Add support for EOSEVM version --- include/evm_runtime/evm_contract.hpp | 63 +++- include/evm_runtime/types.hpp | 10 + src/actions.cpp | 56 +++- tests/CMakeLists.txt | 1 + tests/basic_evm_tester.cpp | 32 +- tests/basic_evm_tester.hpp | 29 +- tests/silkworm/core/silkworm/common/util.hpp | 40 ++- .../core/silkworm/types/transaction.cpp | 6 + tests/version_tests.cpp | 282 ++++++++++++++++++ 9 files changed, 495 insertions(+), 24 deletions(-) create mode 100644 tests/version_tests.cpp diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 672ef687..c741acbb 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -2,11 +2,15 @@ #include #include #include +#include #include #include #include + +#include + #ifdef WITH_TEST_ACTIONS #include #endif @@ -93,6 +97,13 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] void assertnonce(eosio::name account, uint64_t next_nonce); + [[eosio::action]] void setversion(uint64_t version); + + // Events + [[eosio::action]] void evmtx(eosio::ignore event){ + eosio::check(get_sender() == get_self(), "forbidden to call"); + }; + #ifdef WITH_ADMIN_ACTIONS [[eosio::action]] void rmgcstore(uint64_t id); [[eosio::action]] void setkvstore(uint64_t account_id, const bytes& key, const std::optional& value); @@ -116,6 +127,29 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] void testbaldust(const name test); #endif + struct evm_version_type { + struct pending { + uint64_t version; + block_timestamp time; + + bool is_active(time_point_sec genesis_time, block_timestamp current_time)const { + eosevm::block_mapping bm(genesis_time.sec_since_epoch()); + auto current_block_num = bm.timestamp_to_evm_block_num(current_time.to_time_point().time_since_epoch().count()); + auto pending_block_num = bm.timestamp_to_evm_block_num(time.to_time_point().time_since_epoch().count()); + return current_block_num > pending_block_num; + } + }; + + void promote_pending() { + eosio::check(pending_version.has_value(), "no pending version"); + cached_version = pending_version.value().version; + pending_version.reset(); + } + + std::optional pending_version; + uint64_t cached_version=0; + }; + struct [[eosio::table]] [[eosio::contract("evm_contract")]] config { unsigned_int version; // placeholder for future variant index @@ -126,7 +160,31 @@ class [[eosio::contract]] evm_contract : public contract uint32_t miner_cut = 0; uint32_t status = 0; // <- bit mask values from status_flags - EOSLIB_SERIALIZE(config, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)); + binary_extension evm_version; + + std::pair get_version(block_timestamp current_time) { + bool update_required = false; + uint64_t current_version = 0; + if(evm_version.has_value()) { + const auto& pending_version = evm_version.value().pending_version; + if(pending_version.has_value()) { + if( pending_version->is_active(genesis_time, current_time) ) { + update_required = true; + evm_version->promote_pending(); + } + } + current_version = evm_version->cached_version; + } + return std::make_pair(current_version, update_required); + } + + void set_version(uint64_t new_version, block_timestamp current_time) { + auto [current_version, _] = get_version(current_time); + eosio::check(new_version > current_version, "new version must be greater than the active one"); + evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time},current_version}); + } + + EOSLIB_SERIALIZE(config, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)(evm_version)); }; private: @@ -164,6 +222,9 @@ class [[eosio::contract]] evm_contract : public contract // to allow sending through a Bytes (basic_string) w/o copying over to a std::vector void pushtx_bytes(eosio::name miner, const std::basic_string& rlptx); using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx_bytes>; + + bool is_from_self = false; + void push_tx(const silkworm::Transaction& txn, uint64_t current_version); }; diff --git a/include/evm_runtime/types.hpp b/include/evm_runtime/types.hpp index 50a6293c..b843b2b4 100644 --- a/include/evm_runtime/types.hpp +++ b/include/evm_runtime/types.hpp @@ -8,6 +8,7 @@ #include #define TOKEN_ACCOUNT_NAME "eosio.token" +#define MAX_EOSEVM_SUPPORTED_VERSION 1 namespace evm_runtime { using intx::operator""_u256; @@ -75,6 +76,15 @@ namespace evm_runtime { using bridge_message = std::variant; + struct evmtx_v0 { + uint64_t eos_evm_version; + bytes rlptx; + + EOSLIB_SERIALIZE(evmtx_v0, (eos_evm_version)(rlptx)); + }; + + using evmtx_type = std::variant; + } //namespace evm_runtime namespace eosio { diff --git a/src/actions.cpp b/src/actions.cpp index a1afe02c..39f3cc0f 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -8,8 +8,7 @@ #include #include #include - -#include +#include #include // included here so NDEBUG is defined to disable assert macro @@ -193,7 +192,7 @@ void check_result( ValidationResult r, const Transaction& txn, const char* desc Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id) { //when being called as an inline action, clutch in allowance for reserved addresses & signatures by setting from_self=true - const bool from_self = get_sender() == get_self(); + const bool from_self = is_from_self || get_sender() == get_self(); balances balance_table(get_self(), get_self().value); @@ -388,7 +387,6 @@ void evm_contract::process_filtered_messages(const std::vector(msg.value()); - eosio::print("FIL MESSAGE: ", uint64_t(msg_v0.force_atomic), "\n"); const auto& receiver = msg_v0.get_account_as_name(); eosio::check(eosio::is_account(receiver), "receiver is not account"); @@ -443,7 +441,10 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { eosio::check((get_sender() != get_self()) || (miner == get_self()), "unexpected error: EVM contract generated inline pushtx without setting itself as the miner"); - const auto& current_config = _config.get(); + auto current_config = _config.get(); + auto current_time = eosio::current_time_point(); + auto [current_version, update_required] = current_config.get_version(current_time); + std::optional> found_chain_config = lookup_known_chain(current_config.chainid); check( found_chain_config.has_value(), "failed to find expected chain config" ); @@ -451,7 +452,7 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { Block block; eosevm::prepare_block_header(block.header, bm, get_self().value, - bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count())); + bm.timestamp_to_evm_block_num(current_time.time_since_epoch().count())); silkworm::protocol::TrustRuleSet engine{*found_chain_config->second}; @@ -479,6 +480,14 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); + if( current_version >= 1 ) { + auto event = evmtx_type{evmtx_v0{current_version, rlptx}}; + action(std::vector{}, get_self(), "evmtx"_n, event).send(); + } + if( update_required ) { + _config.set(current_config, get_self()); + } + LOGTIME("EVM END"); } @@ -557,7 +566,9 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& b.balance.balance += quantity; }); - const auto& current_config = _config.get(); + auto current_config = _config.get(); + auto current_time = eosio::current_time_point(); + auto [current_version, update_required] = current_config.get_version(current_time); //subtract off the ingress bridge fee from the quantity that will be bridged quantity -= current_config.ingress_bridge_fee; @@ -580,10 +591,7 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& txn.r = 0u; // r == 0 is pseudo signature that resolves to reserved address range txn.s = get_self().value; - Bytes rlp; - rlp::encode(rlp, txn); - pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}}); - pushtx_act.send(get_self(), rlp); + push_tx(txn, current_version); } void evm_contract::transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo) { @@ -626,7 +634,9 @@ bool evm_contract::gc(uint32_t max) { } void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce) { - const auto& current_config = _config.get(); + auto current_config = _config.get(); + auto current_time = eosio::current_time_point(); + auto [current_version, update_required] = current_config.get_version(current_time); Transaction txn; txn.type = TransactionType::kLegacy; @@ -645,10 +655,20 @@ void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, txn.to = to_evmc_address(bv_to); } + push_tx(txn, current_version); +} + +void evm_contract::push_tx(const silkworm::Transaction& txn, uint64_t current_version) { Bytes rlp; rlp::encode(rlp, txn); - pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}}); - pushtx_act.send(get_self(), rlp); + if( current_version >= 1 ) { + is_from_self = true; + auto rlptx = bytes{rlp.begin(), rlp.end()}; + pushtx(get_self(), rlptx); + } else { + pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}}); + pushtx_act.send(get_self(), rlp); + } } void evm_contract::call(eosio::name from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) { @@ -745,4 +765,12 @@ void evm_contract::assertnonce(eosio::name account, uint64_t next_nonce) { } } +void evm_contract::setversion(uint64_t version) { + require_auth(get_self()); + auto config = _config.get(); + eosio::check(version <= MAX_EOSEVM_SUPPORTED_VERSION, "Unsupported version"); + config.set_version(version, eosio::current_block_time()); + _config.set(config, get_self()); +} + } //evm_runtime diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 707b21f9..ddde4253 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -22,6 +22,7 @@ include_directories( ) add_eosio_test_executable( unit_test + ${CMAKE_SOURCE_DIR}/version_tests.cpp ${CMAKE_SOURCE_DIR}/account_id_tests.cpp ${CMAKE_SOURCE_DIR}/basic_evm_tester.cpp ${CMAKE_SOURCE_DIR}/evm_runtime_tests.cpp diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index 7b68a471..858b5115 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -69,6 +69,25 @@ namespace fc { namespace raw { tmp.flags=0; if(ds.remaining()) { fc::raw::unpack(ds, tmp.flags); } } FC_RETHROW_EXCEPTIONS(warn, "error unpacking partial_account_table_row") } + + template<> + inline void unpack( datastream& ds, evm_test::config_table_row& tmp) + { try { + fc::raw::unpack(ds, tmp.version); + fc::raw::unpack(ds, tmp.chainid); + fc::raw::unpack(ds, tmp.genesis_time); + fc::raw::unpack(ds, tmp.ingress_bridge_fee); + fc::raw::unpack(ds, tmp.gas_price); + fc::raw::unpack(ds, tmp.miner_cut); + fc::raw::unpack(ds, tmp.status); + + tmp.evm_version = {}; + if(ds.remaining()) { + evm_test::evm_version_type version; + fc::raw::unpack(ds, version); + tmp.evm_version.emplace(version); + } + } FC_RETHROW_EXCEPTIONS(warn, "error unpacking partial_account_table_row") } }} @@ -327,7 +346,7 @@ transaction_trace_ptr basic_evm_tester::exec(const exec_input& input, const std: 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::call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor) +transaction_trace_ptr basic_evm_tester::call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor) { bytes to_bytes; to_bytes.resize(to.size()); @@ -341,10 +360,10 @@ void basic_evm_tester::call(name from, const evmc::bytes& to, const evmc::bytes& value_bytes.resize(value.size()); memcpy(value_bytes.data(), value.data(), value.size()); - push_action(evm_account_name, "call"_n, actor, mvo()("from", from)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit)); + return push_action(evm_account_name, "call"_n, actor, mvo()("from", from)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit)); } -void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor) +transaction_trace_ptr basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor) { bytes to_bytes; to_bytes.resize(to.size()); @@ -362,7 +381,7 @@ void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, value_bytes.resize(value.size()); memcpy(value_bytes.data(), value.data(), value.size()); - push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit)); + return push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit)); } transaction_trace_ptr basic_evm_tester::bridgereg(name receiver, name handler, asset min_fee, vector extra_signers) { @@ -395,6 +414,11 @@ transaction_trace_ptr basic_evm_tester::pushtx(const silkworm::Transaction& trx, return push_action(evm_account_name, "pushtx"_n, miner, mvo()("miner", miner)("rlptx", rlp_bytes)); } +transaction_trace_ptr basic_evm_tester::setversion(uint64_t version, name actor) { + return basic_evm_tester::push_action(evm_account_name, "setversion"_n, actor, + mvo()("version", version)); +} + transaction_trace_ptr basic_evm_tester::rmgcstore(uint64_t id, name actor) { return basic_evm_tester::push_action(evm_account_name, "rmgcstore"_n, actor, mvo()("id", id)); diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 27c8efe9..65637972 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -48,6 +48,23 @@ void to_variant(const evmc::address& o, fc::variant& v); namespace evm_test { +struct evmtx_v0 { + uint64_t eos_evm_version; + bytes rlptx; +}; + +using evmtx_type = std::variant; + +struct evm_version_type { + struct pending { + uint64_t version; + block_timestamp_type time; + }; + + std::optional pending_version; + uint64_t cached_version=0; +}; + struct config_table_row { unsigned_int version; @@ -57,6 +74,7 @@ struct config_table_row uint64_t gas_price; uint32_t miner_cut; uint32_t status; + std::optional evm_version; }; struct config2_table_row @@ -159,8 +177,9 @@ using bridge_message = std::variant; } // namespace evm_test -FC_REFLECT(evm_test::config_table_row, - (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)) +FC_REFLECT(evm_test::config_table_row, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)(evm_version)) +FC_REFLECT(evm_test::evm_version_type, (pending_version)(cached_version)) +FC_REFLECT(evm_test::evm_version_type::pending, (version)(time)) FC_REFLECT(evm_test::config2_table_row,(next_account_id)) FC_REFLECT(evm_test::balance_and_dust, (balance)(dust)); FC_REFLECT(evm_test::account_object, (id)(address)(nonce)(balance)) @@ -175,6 +194,7 @@ FC_REFLECT(evm_test::message_receiver, (account)(handler)(min_fee)(flags)); FC_REFLECT(evm_test::bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); FC_REFLECT(evm_test::gcstore, (id)(storage_id)); FC_REFLECT(evm_test::account_code, (id)(ref_count)(code)(code_hash)); +FC_REFLECT(evm_test::evmtx_v0, (eos_evm_version)(rlptx)); namespace evm_test { class evm_eoa @@ -379,8 +399,9 @@ class basic_evm_tester : public evm_validating_tester transaction_trace_ptr exec(const exec_input& input, const std::optional& callback); transaction_trace_ptr assertnonce(name account, uint64_t next_nonce); transaction_trace_ptr pushtx(const silkworm::Transaction& trx, name miner = evm_account_name); - void call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor); - void admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor); + transaction_trace_ptr setversion(uint64_t version, name actor); + transaction_trace_ptr call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor); + transaction_trace_ptr admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor); evmc::address deploy_contract(evm_eoa& eoa, evmc::bytes bytecode); void addegress(const std::vector& accounts); diff --git a/tests/silkworm/core/silkworm/common/util.hpp b/tests/silkworm/core/silkworm/common/util.hpp index 67de428c..b3e7cb6a 100644 --- a/tests/silkworm/core/silkworm/common/util.hpp +++ b/tests/silkworm/core/silkworm/common/util.hpp @@ -106,6 +106,22 @@ inline ethash::hash256 keccak256(ByteView view) { return ethash::keccak256(view. // Splits a string by delimiter and returns a vector of tokens std::vector split(std::string_view source, std::string_view delimiter); +inline std::optional extract_reserved_address(const evmc::address& addr) { + constexpr uint8_t reserved_address_prefix[] = {0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb}; + + if(!std::equal(std::begin(reserved_address_prefix), std::end(reserved_address_prefix), static_cast(addr).begin())) + return std::nullopt; + uint64_t reserved; + memcpy(&reserved, static_cast(addr).data()+sizeof(reserved_address_prefix), sizeof(reserved)); + return be64toh(reserved); +} + +inline bool is_reserved_address(const evmc::address& addr) { + return extract_reserved_address(addr) != std::nullopt; +} + inline evmc::address make_reserved_address(uint64_t account) { return evmc_address({0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, @@ -120,6 +136,28 @@ inline evmc::address make_reserved_address(uint64_t account) { static_cast(account >> 0)}); } +inline evmc::address decode_special_signature(const intx::uint256& s) { + // Assumen already tested by is_special_signature() + if (s <= std::numeric_limits::max()) { + return make_reserved_address(static_cast(s)); + } + else { + evmc::address from = evmc::address{}; + intx::be::trunc(from.bytes, s); + return from; + } +} + +inline bool is_special_signature(const intx::uint256& r, const intx::uint256& s) { + // s contains a regular evm_address if padded with '1's + // otherwise it should be an eos name + return r == 0 && + (s <= std::numeric_limits::max() || + s >> kAddressLength * 8 == (~intx::uint256(0)) >> kAddressLength * 8); +} + + + } // namespace silkworm -#endif // SILKWORM_COMMON_UTIL_HPP_ \ No newline at end of file +#endif // SILKWORM_COMMON_UTIL_HPP_ diff --git a/tests/silkworm/core/silkworm/types/transaction.cpp b/tests/silkworm/core/silkworm/types/transaction.cpp index eff2497b..9e3fd74d 100644 --- a/tests/silkworm/core/silkworm/types/transaction.cpp +++ b/tests/silkworm/core/silkworm/types/transaction.cpp @@ -380,6 +380,12 @@ void Transaction::recover_sender() { if (from.has_value()) { return; } + + if(is_special_signature(r, s)) { + from = decode_special_signature(s); + return; + } + Bytes rlp{}; rlp::encode(rlp, *this, /*for_signing=*/true, /*wrap_eip2718_into_array=*/false); ethash::hash256 hash{keccak256(rlp)}; diff --git a/tests/version_tests.cpp b/tests/version_tests.cpp new file mode 100644 index 00000000..554f1db4 --- /dev/null +++ b/tests/version_tests.cpp @@ -0,0 +1,282 @@ +#include "basic_evm_tester.hpp" +#include +#include +#include + +using namespace evm_test; +using eosio::testing::eosio_assert_message_is; + +struct version_tester : basic_evm_tester { + version_tester() { + create_accounts({"alice"_n}); + transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); + init(); + } + + /* + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.2 <0.9.0; + contract Increment { + mapping (address => uint256) values; + function increment() public { + values[msg.sender]++; + } + function retrieve(address a) public view returns (uint256){ + return values[a]; + } + } + */ + + const std::string contract_bytecode = + "608060405234801561001057600080fd5b50610284806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80630a79309b1461003b578063d09de08a1461006b575b600080fd5b61005560048036038101906100509190610176565b610075565b60405161006291906101bc565b60405180910390f35b6100736100bd565b005b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600081548092919061010c90610206565b9190505550565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061014382610118565b9050919050565b61015381610138565b811461015e57600080fd5b50565b6000813590506101708161014a565b92915050565b60006020828403121561018c5761018b610113565b5b600061019a84828501610161565b91505092915050565b6000819050919050565b6101b6816101a3565b82525050565b60006020820190506101d160008301846101ad565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610211826101a3565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610243576102426101d7565b5b60018201905091905056fea26469706673582212205ef4ef3ff462d43a7ce545531c2f77184355d61dcc93de7deb2d57696ceb073164736f6c63430008110033"; + + auto deploy_contract(evm_eoa& eoa, evmc::bytes bytecode) + { + uint64_t nonce = eoa.next_nonce; + + const auto gas_price = get_config().gas_price; + + silkworm::Transaction tx{ + .type = silkworm::Transaction::Type::kLegacy, + .max_priority_fee_per_gas = gas_price, + .max_fee_per_gas = gas_price, + .gas_limit = 10'000'000, + .data = std::move(bytecode), + }; + + eoa.sign(tx); + auto trace = pushtx(tx); + + return std::make_tuple(trace, silkworm::create_address(eoa.address, nonce)); + } + + auto deploy_test_contract(evm_eoa& eoa) { + return deploy_contract(eoa, evmc::from_hex(contract_bytecode).value()); + } + + intx::uint256 retrieve(const evmc::address& contract_addr, const evmc::address& account) { + exec_input input; + input.context = {}; + input.to = bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("0a79309b").value(); // sha3(retrieve(address))[:4] + data += silkworm::to_bytes32(account); + input.data = bytes{data.begin(), data.end()}; + + auto res = exec(input, {}); + auto out = fc::raw::unpack(res->action_traces[0].return_value); + BOOST_REQUIRE(out.status == 0); + BOOST_REQUIRE(out.data.size() == 32); + + auto count = intx::be::unsafe::load(reinterpret_cast(out.data.data())); + return count; + } + +}; + +BOOST_AUTO_TEST_SUITE(verion_evm_tests) +BOOST_FIXTURE_TEST_CASE(set_version, version_tester) try { + + auto config = get_config(); + eosevm::block_mapping bm(config.genesis_time.sec_since_epoch()); + + BOOST_REQUIRE_EXCEPTION(setversion(0, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + BOOST_REQUIRE_EXCEPTION(setversion(0, evm_account_name), + eosio_assert_message_exception, + eosio_assert_message_is("new version must be greater than the active one")); + + BOOST_REQUIRE_EXCEPTION(setversion(2, evm_account_name), + eosio_assert_message_exception, + eosio_assert_message_is("Unsupported version")); + + setversion(1, evm_account_name); + config = get_config(); + + BOOST_CHECK_EQUAL(config.evm_version.has_value(), true); + BOOST_CHECK_EQUAL(config.evm_version.value().cached_version, 0); + BOOST_CHECK_EQUAL(config.evm_version.value().pending_version.has_value(), true); + BOOST_REQUIRE(config.evm_version.value().pending_version.value().time == control->pending_block_time()); + BOOST_REQUIRE(config.evm_version.value().pending_version.value().version == 1); + + // Fund evm1 address with 100 EOS (this will NOT trigger an update of the evm version) + evm_eoa evm1; + const int64_t to_bridge = 1000000; + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + config = get_config(); + BOOST_CHECK_EQUAL(config.evm_version.has_value(), true); + BOOST_CHECK_EQUAL(config.evm_version.value().cached_version, 0); + BOOST_CHECK_EQUAL(config.evm_version.value().pending_version.has_value(), true); + BOOST_REQUIRE(config.evm_version.value().pending_version.value().time == control->pending_block_time()); + BOOST_REQUIRE(config.evm_version.value().pending_version.value().version == 1); + + // Produce blocks until the next EVM block (pending version activation) + const auto& pending_version = config.evm_version.value().pending_version.value(); + auto b0 = bm.timestamp_to_evm_block_num(pending_version.time.to_time_point().time_since_epoch().count()); + while(bm.timestamp_to_evm_block_num(control->pending_block_time().time_since_epoch().count()) <= b0) { + produce_block(); + } + + // Fund evm1 address with 100 more EOS (this will trigger an update of the evm version) + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + config = get_config(); + + BOOST_CHECK_EQUAL(config.evm_version.has_value(), true); + BOOST_CHECK_EQUAL(config.evm_version.value().cached_version, 1); + BOOST_CHECK_EQUAL(config.evm_version.value().pending_version.has_value(), false); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(traces_in_different_eosevm_version, version_tester) try { + + auto config = get_config(); + + evm_eoa evm1; + const int64_t to_bridge = 1000000; + + // Open alice internal balance + open("alice"_n); + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), "alice"); + auto alice_addr = make_reserved_address("alice"_n.to_uint64_t()); + + // Fund evm1 address + // Test traces of `handle_evm_transfer` (EVM VERSION=0) + auto trace = transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + BOOST_REQUIRE(trace->action_traces.size() == 4); + BOOST_REQUIRE(trace->action_traces[0].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[2].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[2].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[3].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[3].act.name == "pushtx"_n); + + // Test traces of `pushtx` (EVM VERSION=0) + auto [trace2, contract_address] = deploy_test_contract(evm1); + BOOST_REQUIRE(trace2->action_traces.size() == 1); + BOOST_REQUIRE(trace2->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace2->action_traces[0].act.name == "pushtx"_n); + + auto to = evmc::bytes{std::begin(contract_address.bytes), std::end(contract_address.bytes)}; + auto data = evmc::from_hex("0xd09de08a"); + + // Test traces of `call` (EVM VERSION=0) + trace = call("alice"_n, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, "alice"_n); + BOOST_REQUIRE(trace->action_traces.size() == 2); + BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "call"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "pushtx"_n); + + // Test traces of `admincall` (EVM VERSION=0) + auto from = evmc::bytes{std::begin(alice_addr.bytes), std::end(alice_addr.bytes)}; + trace = admincall(from, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, evm_account_name); + BOOST_REQUIRE(trace->action_traces.size() == 2); + BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "admincall"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "pushtx"_n); + + ///////////////////////////////////// + /// change EOS EVM VERSION => 1 /// + ///////////////////////////////////// + + setversion(1, evm_account_name); + produce_blocks(2); + + auto get_tx_from_trace = [&](const bytes& v) { + auto evmtx_v = fc::raw::unpack(v.data(), v.size()); + + BOOST_REQUIRE(std::holds_alternative(evmtx_v)); + + const auto& evmtx = std::get(evmtx_v); + BOOST_REQUIRE(evmtx.eos_evm_version == 1); + + silkworm::Transaction tx; + silkworm::ByteView bv{(const uint8_t*)evmtx.rlptx.data(), evmtx.rlptx.size()}; + silkworm::rlp::decode(bv, tx); + BOOST_REQUIRE(bv.empty()); + + return tx; + }; + + // Test traces of `handle_evm_transfer` (EVM VERSION=1) + trace = transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + BOOST_REQUIRE(trace->action_traces.size() == 4); + BOOST_REQUIRE(trace->action_traces[0].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[2].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[2].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[3].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[3].act.name == "evmtx"_n); + + auto tx = get_tx_from_trace(trace->action_traces[3].act.data); + BOOST_REQUIRE(tx.value == intx::uint256(balance_and_dust{make_asset(to_bridge), 0})); + BOOST_REQUIRE(tx.to == evm1.address); + + // Test traces of `pushtx` (EVM VERSION=1) + silkworm::Transaction txin { + .type = silkworm::Transaction::Type::kLegacy, + .max_priority_fee_per_gas = config.gas_price, + .max_fee_per_gas = config.gas_price, + .gas_limit = 10'000'000, + .to = contract_address, + .data = *data + }; + + evm1.sign(txin); + trace = pushtx(txin); + + BOOST_REQUIRE(trace->action_traces.size() == 2); + BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "pushtx"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "evmtx"_n); + + auto txout = get_tx_from_trace(trace->action_traces[1].act.data); + BOOST_REQUIRE(txout == txin); + + // Test traces of `call` (EVM VERSION=1) + trace = call("alice"_n, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, "alice"_n); + BOOST_REQUIRE(trace->action_traces.size() == 2); + BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "call"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "evmtx"_n); + + tx = get_tx_from_trace(trace->action_traces[1].act.data); + tx.recover_sender(); + + BOOST_REQUIRE(tx.value == intx::uint256(0)); + BOOST_REQUIRE(tx.to == contract_address); + BOOST_REQUIRE(tx.data == *data); + BOOST_REQUIRE(*tx.from == alice_addr); + + // Test traces of `admincall` (EVM VERSION=1) + trace = admincall(from, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, evm_account_name); + BOOST_REQUIRE(trace->action_traces.size() == 2); + BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.name == "admincall"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.name == "evmtx"_n); + + tx = get_tx_from_trace(trace->action_traces[1].act.data); + tx.recover_sender(); + + BOOST_REQUIRE(tx.value == intx::uint256(0)); + BOOST_REQUIRE(tx.to == contract_address); + BOOST_REQUIRE(tx.data == *data); + BOOST_REQUIRE(*tx.from == alice_addr); + + // Check 4 times call to `increment` from alice_addr + BOOST_REQUIRE(retrieve(contract_address, alice_addr) == intx::uint256(4)); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() From 2af09938648b3c380c016ea0c5431b83ef0ba5d4 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 2 Jan 2024 18:47:50 -0300 Subject: [PATCH 02/23] Remove unused include file --- src/actions.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/actions.cpp b/src/actions.cpp index f2100546..e88a9092 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include // included here so NDEBUG is defined to disable assert macro From 723a1d214d9d8176d8cc416d150f5aa55f4f4a69 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 9 Jan 2024 22:06:44 -0300 Subject: [PATCH 03/23] Change pending time to time_point --- include/evm_runtime/evm_contract.hpp | 6 +++--- tests/basic_evm_tester.hpp | 2 +- tests/version_tests.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index c741acbb..b4d30521 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -130,12 +130,12 @@ class [[eosio::contract]] evm_contract : public contract struct evm_version_type { struct pending { uint64_t version; - block_timestamp time; + time_point time; bool is_active(time_point_sec genesis_time, block_timestamp current_time)const { eosevm::block_mapping bm(genesis_time.sec_since_epoch()); auto current_block_num = bm.timestamp_to_evm_block_num(current_time.to_time_point().time_since_epoch().count()); - auto pending_block_num = bm.timestamp_to_evm_block_num(time.to_time_point().time_since_epoch().count()); + auto pending_block_num = bm.timestamp_to_evm_block_num(time.time_since_epoch().count()); return current_block_num > pending_block_num; } }; @@ -181,7 +181,7 @@ class [[eosio::contract]] evm_contract : public contract void set_version(uint64_t new_version, block_timestamp current_time) { auto [current_version, _] = get_version(current_time); eosio::check(new_version > current_version, "new version must be greater than the active one"); - evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time},current_version}); + evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time.to_time_point()},current_version}); } EOSLIB_SERIALIZE(config, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)(evm_version)); diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 65637972..85afe70f 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -58,7 +58,7 @@ using evmtx_type = std::variant; struct evm_version_type { struct pending { uint64_t version; - block_timestamp_type time; + fc::time_point time; }; std::optional pending_version; diff --git a/tests/version_tests.cpp b/tests/version_tests.cpp index 554f1db4..bcbd65cf 100644 --- a/tests/version_tests.cpp +++ b/tests/version_tests.cpp @@ -115,7 +115,7 @@ BOOST_FIXTURE_TEST_CASE(set_version, version_tester) try { // Produce blocks until the next EVM block (pending version activation) const auto& pending_version = config.evm_version.value().pending_version.value(); - auto b0 = bm.timestamp_to_evm_block_num(pending_version.time.to_time_point().time_since_epoch().count()); + auto b0 = bm.timestamp_to_evm_block_num(pending_version.time.time_since_epoch().count()); while(bm.timestamp_to_evm_block_num(control->pending_block_time().time_since_epoch().count()) <= b0) { produce_block(); } From 8ec1275073ea3be45069ec66f72520c1da85d97c Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 9 Jan 2024 22:11:39 -0300 Subject: [PATCH 04/23] Make get_version const and call promote_pending from outside --- include/evm_runtime/evm_contract.hpp | 17 ++++++++++------- src/actions.cpp | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index b4d30521..cca26aff 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -162,18 +162,17 @@ class [[eosio::contract]] evm_contract : public contract binary_extension evm_version; - std::pair get_version(block_timestamp current_time) { + std::pair get_version(block_timestamp current_time)const { bool update_required = false; uint64_t current_version = 0; if(evm_version.has_value()) { const auto& pending_version = evm_version.value().pending_version; - if(pending_version.has_value()) { - if( pending_version->is_active(genesis_time, current_time) ) { - update_required = true; - evm_version->promote_pending(); - } + if(pending_version.has_value() && pending_version->is_active(genesis_time, current_time)) { + update_required = true; + current_version = evm_version->pending_version->version; + } else { + current_version = evm_version->cached_version; } - current_version = evm_version->cached_version; } return std::make_pair(current_version, update_required); } @@ -181,6 +180,10 @@ class [[eosio::contract]] evm_contract : public contract void set_version(uint64_t new_version, block_timestamp current_time) { auto [current_version, _] = get_version(current_time); eosio::check(new_version > current_version, "new version must be greater than the active one"); + if( update_required ) { + eosio::check(evm_version.has_value(), "no evm_version"); + evm_version->promote_pending(); + } evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time.to_time_point()},current_version}); } diff --git a/src/actions.cpp b/src/actions.cpp index e88a9092..537047fe 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -484,9 +484,10 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { action(std::vector{}, get_self(), "evmtx"_n, event).send(); } if( update_required ) { + eosio::check(current_config.evm_version.has_value(), "no evm_version"); + current_config.evm_version->promote_pending(); _config.set(current_config, get_self()); } - LOGTIME("EVM END"); } From cc048dba5987c3326a66456235532f8e0d1589f2 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 9 Jan 2024 22:14:57 -0300 Subject: [PATCH 05/23] Refactor pushtx action call --- include/evm_runtime/evm_contract.hpp | 9 ++++---- src/actions.cpp | 34 +++++++++++++++------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index cca26aff..6a66b4bc 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -178,7 +178,8 @@ class [[eosio::contract]] evm_contract : public contract } void set_version(uint64_t new_version, block_timestamp current_time) { - auto [current_version, _] = get_version(current_time); + eosio::check(new_version <= MAX_EOSEVM_SUPPORTED_VERSION, "Unsupported version"); + auto [current_version, update_required] = get_version(current_time); eosio::check(new_version > current_version, "new version must be greater than the active one"); if( update_required ) { eosio::check(evm_version.has_value(), "no evm_version"); @@ -210,7 +211,7 @@ class [[eosio::contract]] evm_contract : public contract check((_config.get().status & static_cast(status_flags::frozen)) == 0, "contract is frozen"); } - silkworm::Receipt execute_tx(eosio::name miner, silkworm::Block& block, silkworm::Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id); + silkworm::Receipt execute_tx(eosio::name miner, silkworm::Block& block, silkworm::Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id, bool is_from_self); void process_filtered_messages(const std::vector& filtered_messages); uint64_t get_and_increment_nonce(const name owner); @@ -226,8 +227,8 @@ class [[eosio::contract]] evm_contract : public contract void pushtx_bytes(eosio::name miner, const std::basic_string& rlptx); using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx_bytes>; - bool is_from_self = false; - void push_tx(const silkworm::Transaction& txn, uint64_t current_version); + void pushtx( eosio::name miner, const bytes& rlptx, silkworm::Transaction& txn, bool is_from_self ); + void versioned_tx_dispatch(silkworm::Transaction& txn, uint64_t current_version); }; diff --git a/src/actions.cpp b/src/actions.cpp index 537047fe..2ba1f3f9 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -189,7 +189,7 @@ void check_result( ValidationResult r, const Transaction& txn, const char* desc eosio::check( false, std::move(err_msg)); } -Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id) { +Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id, bool is_from_self) { //when being called as an inline action, clutch in allowance for reserved addresses & signatures by setting from_self=true const bool from_self = is_from_self || get_sender() == get_self(); @@ -432,9 +432,8 @@ void evm_contract::process_filtered_messages(const std::vectorsecond}; - Transaction tx; - ByteView bv{(const uint8_t*)rlptx.data(), rlptx.size()}; - eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); - LOGTIME("EVM TX DECODE"); - check(tx.max_priority_fee_per_gas == tx.max_fee_per_gas, "max_priority_fee_per_gas must be equal to max_fee_per_gas"); check(tx.max_fee_per_gas >= current_config.gas_price, "gas price is too low"); @@ -473,7 +467,7 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { return message.recipient == me && message.input_size > 0; }); - auto receipt = execute_tx(miner, block, tx, ep, true); + auto receipt = execute_tx(miner, block, tx, ep, true, is_from_self); process_filtered_messages(ep.state().filtered_messages()); @@ -491,6 +485,16 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { LOGTIME("EVM END"); } +void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { + LOGTIME("EVM START0"); + + Transaction tx; + ByteView bv{(const uint8_t*)rlptx.data(), rlptx.size()}; + eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); + + pushtx(miner, rlptx, tx, false); +} + void evm_contract::open(eosio::name owner) { assert_unfrozen(); require_auth(owner); @@ -591,7 +595,7 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& txn.r = 0u; // r == 0 is pseudo signature that resolves to reserved address range txn.s = get_self().value; - push_tx(txn, current_version); + versioned_tx_dispatch(txn, current_version); } void evm_contract::transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo) { @@ -657,16 +661,15 @@ void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, txn.to = to_evmc_address(bv_to); } - push_tx(txn, current_version); + versioned_tx_dispatch(txn, current_version); } -void evm_contract::push_tx(const silkworm::Transaction& txn, uint64_t current_version) { +void evm_contract::versioned_tx_dispatch(silkworm::Transaction& txn, uint64_t current_version) { Bytes rlp; rlp::encode(rlp, txn); if( current_version >= 1 ) { - is_from_self = true; auto rlptx = bytes{rlp.begin(), rlp.end()}; - pushtx(get_self(), rlptx); + pushtx(get_self(), rlptx, txn, true); } else { pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}}); pushtx_act.send(get_self(), rlp); @@ -770,7 +773,6 @@ void evm_contract::assertnonce(eosio::name account, uint64_t next_nonce) { void evm_contract::setversion(uint64_t version) { require_auth(get_self()); auto config = _config.get(); - eosio::check(version <= MAX_EOSEVM_SUPPORTED_VERSION, "Unsupported version"); config.set_version(version, eosio::current_block_time()); _config.set(config, get_self()); } From e844094678633b951927b3553d7a106f0129a89f Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 9 Jan 2024 22:16:21 -0300 Subject: [PATCH 06/23] Add tests to check that admincall/call will promote version and exec will not --- tests/version_tests.cpp | 183 +++++++++++++++++++++++++++++++++++----- 1 file changed, 162 insertions(+), 21 deletions(-) diff --git a/tests/version_tests.cpp b/tests/version_tests.cpp index bcbd65cf..499f1477 100644 --- a/tests/version_tests.cpp +++ b/tests/version_tests.cpp @@ -7,6 +7,10 @@ using namespace evm_test; using eosio::testing::eosio_assert_message_is; struct version_tester : basic_evm_tester { + + static constexpr char* increment_ = "0xd09de08a"; // sha3(increment())[:4] + static constexpr char* retrieve_ = "0x0a79309b"; // sha3(retrieve(address))[:4] + version_tester() { create_accounts({"alice"_n}); transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); @@ -60,7 +64,7 @@ struct version_tester : basic_evm_tester { input.to = bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; silkworm::Bytes data; - data += evmc::from_hex("0a79309b").value(); // sha3(retrieve(address))[:4] + data += evmc::from_hex(retrieve_).value(); data += silkworm::to_bytes32(account); input.data = bytes{data.begin(), data.end()}; @@ -146,39 +150,39 @@ BOOST_FIXTURE_TEST_CASE(traces_in_different_eosevm_version, version_tester) try // Test traces of `handle_evm_transfer` (EVM VERSION=0) auto trace = transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); BOOST_REQUIRE(trace->action_traces.size() == 4); - BOOST_REQUIRE(trace->action_traces[0].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == token_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "transfer"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == token_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "transfer"_n); - BOOST_REQUIRE(trace->action_traces[2].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[2].act.account == token_account_name); BOOST_REQUIRE(trace->action_traces[2].act.name == "transfer"_n); - BOOST_REQUIRE(trace->action_traces[3].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[3].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[3].act.name == "pushtx"_n); // Test traces of `pushtx` (EVM VERSION=0) auto [trace2, contract_address] = deploy_test_contract(evm1); BOOST_REQUIRE(trace2->action_traces.size() == 1); - BOOST_REQUIRE(trace2->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace2->action_traces[0].act.account == evm_account_name); BOOST_REQUIRE(trace2->action_traces[0].act.name == "pushtx"_n); auto to = evmc::bytes{std::begin(contract_address.bytes), std::end(contract_address.bytes)}; - auto data = evmc::from_hex("0xd09de08a"); + auto data = evmc::from_hex(increment_); // Test traces of `call` (EVM VERSION=0) trace = call("alice"_n, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, "alice"_n); BOOST_REQUIRE(trace->action_traces.size() == 2); - BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "call"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "pushtx"_n); // Test traces of `admincall` (EVM VERSION=0) auto from = evmc::bytes{std::begin(alice_addr.bytes), std::end(alice_addr.bytes)}; trace = admincall(from, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, evm_account_name); BOOST_REQUIRE(trace->action_traces.size() == 2); - BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "admincall"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "pushtx"_n); ///////////////////////////////////// @@ -207,13 +211,13 @@ BOOST_FIXTURE_TEST_CASE(traces_in_different_eosevm_version, version_tester) try // Test traces of `handle_evm_transfer` (EVM VERSION=1) trace = transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); BOOST_REQUIRE(trace->action_traces.size() == 4); - BOOST_REQUIRE(trace->action_traces[0].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == token_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "transfer"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == token_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "transfer"_n); - BOOST_REQUIRE(trace->action_traces[2].act.account == "eosio.token"_n); + BOOST_REQUIRE(trace->action_traces[2].act.account == token_account_name); BOOST_REQUIRE(trace->action_traces[2].act.name == "transfer"_n); - BOOST_REQUIRE(trace->action_traces[3].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[3].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[3].act.name == "evmtx"_n); auto tx = get_tx_from_trace(trace->action_traces[3].act.data); @@ -234,20 +238,21 @@ BOOST_FIXTURE_TEST_CASE(traces_in_different_eosevm_version, version_tester) try trace = pushtx(txin); BOOST_REQUIRE(trace->action_traces.size() == 2); - BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "pushtx"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "evmtx"_n); auto txout = get_tx_from_trace(trace->action_traces[1].act.data); BOOST_REQUIRE(txout == txin); + BOOST_REQUIRE(retrieve(contract_address, evm1.address) == intx::uint256(1)); // Test traces of `call` (EVM VERSION=1) trace = call("alice"_n, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, "alice"_n); BOOST_REQUIRE(trace->action_traces.size() == 2); - BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "call"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "evmtx"_n); tx = get_tx_from_trace(trace->action_traces[1].act.data); @@ -261,9 +266,9 @@ BOOST_FIXTURE_TEST_CASE(traces_in_different_eosevm_version, version_tester) try // Test traces of `admincall` (EVM VERSION=1) trace = admincall(from, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, evm_account_name); BOOST_REQUIRE(trace->action_traces.size() == 2); - BOOST_REQUIRE(trace->action_traces[0].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[0].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[0].act.name == "admincall"_n); - BOOST_REQUIRE(trace->action_traces[1].act.account == "evm"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == evm_account_name); BOOST_REQUIRE(trace->action_traces[1].act.name == "evmtx"_n); tx = get_tx_from_trace(trace->action_traces[1].act.data); @@ -279,4 +284,140 @@ BOOST_FIXTURE_TEST_CASE(traces_in_different_eosevm_version, version_tester) try } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE(exec_does_not_update_version, version_tester) try { + auto config = get_config(); + + evm_eoa evm1; + const int64_t to_bridge = 1000000; + + // Fund evm1 address + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + // Deploy contract + auto [_, contract_address] = deploy_test_contract(evm1); + + // Call `increment` from evm1 address + silkworm::Transaction txin { + .type = silkworm::Transaction::Type::kLegacy, + .max_priority_fee_per_gas = config.gas_price, + .max_fee_per_gas = config.gas_price, + .gas_limit = 10'000'000, + .to = contract_address, + .data = *evmc::from_hex(increment_) + }; + + evm1.sign(txin); + pushtx(txin); + + setversion(1, evm_account_name); + auto activation_time = control->pending_block_time(); + produce_blocks(10); + + // Use `exec` action + BOOST_REQUIRE(retrieve(contract_address, evm1.address) == intx::uint256(1)); + + // Validate config still at 0 + config = get_config(); + BOOST_CHECK_EQUAL(config.evm_version.has_value(), true); + BOOST_CHECK_EQUAL(config.evm_version.value().cached_version, 0); + BOOST_CHECK_EQUAL(config.evm_version.value().pending_version.has_value(), true); + BOOST_REQUIRE(config.evm_version.value().pending_version.value().time == activation_time); + BOOST_REQUIRE(config.evm_version.value().pending_version.value().version == 1); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(call_promote_pending, version_tester) try { + auto config = get_config(); + + evm_eoa evm1; + const int64_t to_bridge = 1000000; + + // Open alice internal balance + open("alice"_n); + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), "alice"); + auto alice_addr = make_reserved_address("alice"_n.to_uint64_t()); + + // Fund evm1 address + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + // Deploy contract + auto [_, contract_address] = deploy_test_contract(evm1); + + // Call `increment` from evm1 address + silkworm::Transaction txin { + .type = silkworm::Transaction::Type::kLegacy, + .max_priority_fee_per_gas = config.gas_price, + .max_fee_per_gas = config.gas_price, + .gas_limit = 10'000'000, + .to = contract_address, + .data = *evmc::from_hex(increment_) + }; + + evm1.sign(txin); + pushtx(txin); + + setversion(1, evm_account_name); + produce_blocks(2); + + // Call increment as allice using `call` action + auto to = evmc::bytes{std::begin(contract_address.bytes), std::end(contract_address.bytes)}; + auto data = evmc::from_hex(increment_); + call("alice"_n, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, "alice"_n); + + // Validate config is now 1 + config = get_config(); + BOOST_CHECK_EQUAL(config.evm_version.has_value(), true); + BOOST_CHECK_EQUAL(config.evm_version.value().cached_version, 1); + BOOST_CHECK_EQUAL(config.evm_version.value().pending_version.has_value(), false); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(admincall_promote_pending, version_tester) try { + auto config = get_config(); + + evm_eoa evm1; + const int64_t to_bridge = 1000000; + + // Open alice internal balance + open("alice"_n); + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), "alice"); + auto alice_addr = make_reserved_address("alice"_n.to_uint64_t()); + + // Fund evm1 address + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + // Deploy contract + auto [_, contract_address] = deploy_test_contract(evm1); + + // Call `increment` from evm1 address + silkworm::Transaction txin { + .type = silkworm::Transaction::Type::kLegacy, + .max_priority_fee_per_gas = config.gas_price, + .max_fee_per_gas = config.gas_price, + .gas_limit = 10'000'000, + .to = contract_address, + .data = *evmc::from_hex(increment_) + }; + + evm1.sign(txin); + pushtx(txin); + + setversion(1, evm_account_name); + produce_blocks(2); + + // Call increment as allice using `call` action + auto to = evmc::bytes{std::begin(contract_address.bytes), std::end(contract_address.bytes)}; + auto data = evmc::from_hex(increment_); + auto from = evmc::bytes{std::begin(alice_addr.bytes), std::end(alice_addr.bytes)}; + admincall(from, to, silkworm::Bytes(evmc::bytes32{}), *data, 1000000, evm_account_name); + + // Validate config is now 1 + config = get_config(); + BOOST_CHECK_EQUAL(config.evm_version.has_value(), true); + BOOST_CHECK_EQUAL(config.evm_version.value().cached_version, 1); + BOOST_CHECK_EQUAL(config.evm_version.value().pending_version.has_value(), false); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() From 85c65f8a03aa7c6a45b36db5f4161cf5e99b87ae Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 9 Jan 2024 22:17:02 -0300 Subject: [PATCH 07/23] Make modifications to init_tests to work with the new refactored pushtx --- tests/basic_evm_tester.hpp | 9 +++++++++ tests/init_tests.cpp | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 85afe70f..f7a056dc 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -461,6 +461,15 @@ class basic_evm_tester : public evm_validating_tester bool scan_gcstore(std::function visitor) const; bool scan_account_code(std::function visitor) const; void scan_balances(std::function visitor) const; + + bytes get_dummy_transaction() { + silkworm::Bytes rlp; + silkworm::rlp::encode(rlp, silkworm::Transaction{}); + bytes rlp_bytes; + rlp_bytes.resize(rlp.size()); + memcpy(rlp_bytes.data(), rlp.data(), rlp.size()); + return rlp_bytes; + } }; inline constexpr intx::uint256 operator"" _wei(const char* s) { return intx::from_string(s); } diff --git a/tests/init_tests.cpp b/tests/init_tests.cpp index 04415aa8..f6c69781 100644 --- a/tests/init_tests.cpp +++ b/tests/init_tests.cpp @@ -6,7 +6,7 @@ BOOST_AUTO_TEST_SUITE(evm_init_tests) BOOST_FIXTURE_TEST_CASE(check_init, basic_evm_tester) try { //all of these should fail since the contract hasn't been init'd yet - BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", bytes())), + BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", get_dummy_transaction())), eosio_assert_message_exception, [](const eosio_assert_message_exception& e) {return testing::expect_assert_message(e, "assertion failure with message: contract not initialized");}); @@ -82,7 +82,7 @@ BOOST_FIXTURE_TEST_CASE(check_freeze, basic_evm_tester) try { push_action("evm"_n, "freeze"_n, "evm"_n, mvo()("value", true)); - BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", bytes())), + BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", get_dummy_transaction())), eosio_assert_message_exception, [](const eosio_assert_message_exception& e) {return testing::expect_assert_message(e, "assertion failure with message: contract is frozen");}); From 005b380b2e4a5a46a02f565357f053e2053b63e3 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 10 Jan 2024 03:22:11 -0300 Subject: [PATCH 08/23] Fix call to execute_tx in testtx action --- src/test_actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_actions.cpp b/src/test_actions.cpp index 6e802f35..ba8bd5c6 100644 --- a/src/test_actions.cpp +++ b/src/test_actions.cpp @@ -25,7 +25,7 @@ using namespace silkworm; ByteView bv{(const uint8_t*)orlptx->data(), orlptx->size()}; eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); - execute_tx(eosio::name{}, block, tx, ep, false); + execute_tx(eosio::name{}, block, tx, ep, false, false); } engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); From f7beb99d43dec0ec79e1c9e920e11b92af5a740e Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Mon, 15 Jan 2024 13:09:35 -0300 Subject: [PATCH 09/23] Revert "Make modifications to init_tests to work with the new refactored pushtx" This reverts commit 85c65f8a03aa7c6a45b36db5f4161cf5e99b87ae. --- tests/basic_evm_tester.hpp | 9 --------- tests/init_tests.cpp | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index f7a056dc..85afe70f 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -461,15 +461,6 @@ class basic_evm_tester : public evm_validating_tester bool scan_gcstore(std::function visitor) const; bool scan_account_code(std::function visitor) const; void scan_balances(std::function visitor) const; - - bytes get_dummy_transaction() { - silkworm::Bytes rlp; - silkworm::rlp::encode(rlp, silkworm::Transaction{}); - bytes rlp_bytes; - rlp_bytes.resize(rlp.size()); - memcpy(rlp_bytes.data(), rlp.data(), rlp.size()); - return rlp_bytes; - } }; inline constexpr intx::uint256 operator"" _wei(const char* s) { return intx::from_string(s); } diff --git a/tests/init_tests.cpp b/tests/init_tests.cpp index f6c69781..04415aa8 100644 --- a/tests/init_tests.cpp +++ b/tests/init_tests.cpp @@ -6,7 +6,7 @@ BOOST_AUTO_TEST_SUITE(evm_init_tests) BOOST_FIXTURE_TEST_CASE(check_init, basic_evm_tester) try { //all of these should fail since the contract hasn't been init'd yet - BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", get_dummy_transaction())), + BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", bytes())), eosio_assert_message_exception, [](const eosio_assert_message_exception& e) {return testing::expect_assert_message(e, "assertion failure with message: contract not initialized");}); @@ -82,7 +82,7 @@ BOOST_FIXTURE_TEST_CASE(check_freeze, basic_evm_tester) try { push_action("evm"_n, "freeze"_n, "evm"_n, mvo()("value", true)); - BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", get_dummy_transaction())), + BOOST_REQUIRE_EXCEPTION(push_action("evm"_n, "pushtx"_n, "evm"_n, mvo()("miner", "evm"_n)("rlptx", bytes())), eosio_assert_message_exception, [](const eosio_assert_message_exception& e) {return testing::expect_assert_message(e, "assertion failure with message: contract is frozen");}); From 074ca0d61fe5b49da7099c0a9d06186fe8012130 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 17 Jan 2024 03:02:36 -0300 Subject: [PATCH 10/23] Refactoring of configuration management and transaction processing refinements --- include/evm_runtime/bridge.hpp | 4 + include/evm_runtime/config_wrapper.hpp | 128 ++++++++++++++++ include/evm_runtime/evm_contract.hpp | 104 ++----------- include/evm_runtime/runtime_config.hpp | 12 ++ include/evm_runtime/tables.hpp | 65 ++++++++ include/evm_runtime/transaction.hpp | 46 ++++++ include/evm_runtime/types.hpp | 1 + src/actions.cpp | 202 ++++++++++++------------- src/test_actions.cpp | 10 +- 9 files changed, 371 insertions(+), 201 deletions(-) create mode 100644 include/evm_runtime/config_wrapper.hpp create mode 100644 include/evm_runtime/runtime_config.hpp create mode 100644 include/evm_runtime/transaction.hpp diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp index d931cecd..c406aba8 100644 --- a/include/evm_runtime/bridge.hpp +++ b/include/evm_runtime/bridge.hpp @@ -1,9 +1,13 @@ #pragma once #include +#include namespace evm_runtime { namespace bridge { +using namespace std; +using namespace eosio; +using namespace silkworm; struct message_v0 { static constexpr uint32_t id = 0xf781185b; //sha3('bridgeMsgV0(string,bool,bytes)')[:4] diff --git a/include/evm_runtime/config_wrapper.hpp b/include/evm_runtime/config_wrapper.hpp new file mode 100644 index 00000000..df32f152 --- /dev/null +++ b/include/evm_runtime/config_wrapper.hpp @@ -0,0 +1,128 @@ +#pragma once +#include +#include + +namespace evm_runtime { + +using eosio::check; + +struct config_wrapper { + + config_wrapper(eosio::name self) : _self(self), _config(self, self.value) { + _exists = _config.exists(); + if(_exists) { + _cached_config = _config.get(); + } + } + + ~config_wrapper() { + flush(); + } + + void flush() { + if( _evm_version_promote_required ) { + promote_pending_evm_version(); + set_dirty(); + } + if(!is_dirty()) { + return; + } + _config.set(_cached_config, _self); + clear_dirty(); + _exists = true; + } + + bool exists() { + return _exists; + } + + inline eosio::unsigned_int get_version()const { return _cached_config.version; } + void set_version(const eosio::unsigned_int version) { _cached_config.version = version; set_dirty(); } + + inline uint64_t get_chainid()const { return _cached_config.chainid; } + void set_chainid(uint64_t chainid) { _cached_config.chainid = chainid; set_dirty(); } + + inline const eosio::time_point_sec& get_genesis_time()const { return _cached_config.genesis_time; } + void set_genesis_time(eosio::time_point_sec genesis_time) { _cached_config.genesis_time = genesis_time; set_dirty(); } + + inline const eosio::asset& get_ingress_bridge_fee()const { return _cached_config.ingress_bridge_fee; } + void set_ingress_bridge_fee(const eosio::asset& ingress_bridge_fee) { _cached_config.ingress_bridge_fee = ingress_bridge_fee; set_dirty(); } + + inline uint64_t get_gas_price()const { return _cached_config.gas_price; } + void set_gas_price(uint64_t gas_price) { _cached_config.gas_price = gas_price; set_dirty(); } + + inline uint32_t get_miner_cut()const { return _cached_config.miner_cut; } + void set_miner_cut(uint32_t miner_cut) { _cached_config.miner_cut = miner_cut; set_dirty(); } + + inline uint32_t get_status()const { return _cached_config.status; } + void set_status(uint32_t status) { _cached_config.status = status; set_dirty(); } + + inline uint64_t get_evm_version()const { + uint64_t current_version = 0; + _evm_version_promote_required = false; + if(_cached_config.evm_version.has_value()) { + std::tie(current_version, _evm_version_promote_required) = _cached_config.evm_version->get_version(_cached_config.genesis_time, eosio::current_time_point()); + } + return current_version; + } + + void set_evm_version(uint64_t new_version) { + eosio::check(new_version <= MAX_EOSEVM_SUPPORTED_VERSION, "Unsupported version"); + auto current_version = get_evm_version(); + eosio::check(new_version > current_version, "new version must be greater than the active one"); + if( _evm_version_promote_required ) { + promote_pending_evm_version(); + } + auto current_time = eosio::current_time_point(); + _cached_config.evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time}, current_version}); + set_dirty(); + } + + void promote_pending_evm_version() { + eosio::check(_cached_config.evm_version.has_value(), "no evm_version"); + _cached_config.evm_version->promote_pending(); + _evm_version_promote_required = false; + } + + void set_fee_parameters(const fee_parameters& fee_params, + bool allow_any_to_be_unspecified) + { + if (fee_params.gas_price.has_value()) { + _cached_config.gas_price = *fee_params.gas_price; + } else { + check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing gas_price"); + } + + if (fee_params.miner_cut.has_value()) { + check(*fee_params.miner_cut <= hundred_percent, "miner_cut cannot exceed 100,000 (100%)"); + + _cached_config.miner_cut = *fee_params.miner_cut; + } else { + check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing miner_cut"); + } + + if (fee_params.ingress_bridge_fee.has_value()) { + check(fee_params.ingress_bridge_fee->symbol == token_symbol, "unexpected bridge symbol"); + check(fee_params.ingress_bridge_fee->amount >= 0, "ingress bridge fee cannot be negative"); + + _cached_config.ingress_bridge_fee = *fee_params.ingress_bridge_fee; + } + + set_dirty(); + } + +private: + inline bool is_dirty()const { return _dirty; } + inline void set_dirty() { _dirty=true; } + inline void clear_dirty() { _dirty=false; } + + bool _dirty = false; + bool _exists = false; + mutable bool _evm_version_promote_required = false; + config _cached_config; + + eosio::name _self; + eosio::singleton<"config"_n, config> _config; +}; + +} //namespace evm_runtime \ No newline at end of file diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 6a66b4bc..07a8c0b8 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -5,12 +5,13 @@ #include #include +#include +#include +#include #include #include -#include - #ifdef WITH_TEST_ACTIONS #include #endif @@ -23,20 +24,8 @@ class [[eosio::contract]] evm_contract : public contract { public: using contract::contract; + evm_contract(eosio::name receiver, eosio::name code, const datastream& ds) : contract(receiver, code, ds), _config(get_self()) {} - struct fee_parameters - { - std::optional gas_price; ///< Minimum gas price (in 10^-18 EOS, aka wei) that is enforced on all - ///< transactions. Required during initialization. - - std::optional miner_cut; ///< Percentage cut (maximum allowed value of 100,000 which equals 100%) of the - ///< gas fee collected for a transaction that is sent to the indicated miner of - ///< that transaction. Required during initialization. - - std::optional ingress_bridge_fee; ///< Fee (in EOS) deducted from ingress transfers of EOS across bridge. - ///< Symbol must be in EOS and quantity must be non-negative. If not - ///< provided during initialization, the default fee of 0 will be used. - }; /** * @brief Initialize EVM contract @@ -74,7 +63,7 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] void exec(const exec_input& input, const std::optional& callback); - [[eosio::action]] void pushtx(eosio::name miner, const bytes& rlptx); + [[eosio::action]] void pushtx(eosio::name miner, bytes& rlptx); [[eosio::action]] void open(eosio::name owner); @@ -127,91 +116,28 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] void testbaldust(const name test); #endif - struct evm_version_type { - struct pending { - uint64_t version; - time_point time; - - bool is_active(time_point_sec genesis_time, block_timestamp current_time)const { - eosevm::block_mapping bm(genesis_time.sec_since_epoch()); - auto current_block_num = bm.timestamp_to_evm_block_num(current_time.to_time_point().time_since_epoch().count()); - auto pending_block_num = bm.timestamp_to_evm_block_num(time.time_since_epoch().count()); - return current_block_num > pending_block_num; - } - }; - - void promote_pending() { - eosio::check(pending_version.has_value(), "no pending version"); - cached_version = pending_version.value().version; - pending_version.reset(); - } - - std::optional pending_version; - uint64_t cached_version=0; - }; - - struct [[eosio::table]] [[eosio::contract("evm_contract")]] config - { - unsigned_int version; // placeholder for future variant index - uint64_t chainid = 0; - time_point_sec genesis_time; - asset ingress_bridge_fee = asset(0, token_symbol); - uint64_t gas_price = 0; - uint32_t miner_cut = 0; - uint32_t status = 0; // <- bit mask values from status_flags - - binary_extension evm_version; - - std::pair get_version(block_timestamp current_time)const { - bool update_required = false; - uint64_t current_version = 0; - if(evm_version.has_value()) { - const auto& pending_version = evm_version.value().pending_version; - if(pending_version.has_value() && pending_version->is_active(genesis_time, current_time)) { - update_required = true; - current_version = evm_version->pending_version->version; - } else { - current_version = evm_version->cached_version; - } - } - return std::make_pair(current_version, update_required); - } - - void set_version(uint64_t new_version, block_timestamp current_time) { - eosio::check(new_version <= MAX_EOSEVM_SUPPORTED_VERSION, "Unsupported version"); - auto [current_version, update_required] = get_version(current_time); - eosio::check(new_version > current_version, "new version must be greater than the active one"); - if( update_required ) { - eosio::check(evm_version.has_value(), "no evm_version"); - evm_version->promote_pending(); - } - evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time.to_time_point()},current_version}); - } - - EOSLIB_SERIALIZE(config, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)(evm_version)); - }; - private: + void open_internal_balance(eosio::name owner); + config_wrapper _config; + enum class status_flags : uint32_t { frozen = 0x1 }; - eosio::singleton<"config"_n, config> _config{get_self(), get_self().value}; - void assert_inited() { check(_config.exists(), "contract not initialized"); - check(_config.get().version == 0u, "unsupported configuration singleton"); + check(_config.get_version() == 0u, "unsupported configuration singleton"); } void assert_unfrozen() { assert_inited(); - check((_config.get().status & static_cast(status_flags::frozen)) == 0, "contract is frozen"); + check((_config.get_status() & static_cast(status_flags::frozen)) == 0, "contract is frozen"); } - silkworm::Receipt execute_tx(eosio::name miner, silkworm::Block& block, silkworm::Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id, bool is_from_self); + silkworm::Receipt execute_tx(const runtime_config& rc, eosio::name miner, silkworm::Block& block, silkworm::Transaction& tx, silkworm::ExecutionProcessor& ep); void process_filtered_messages(const std::vector& filtered_messages); uint64_t get_and_increment_nonce(const name owner); @@ -223,12 +149,10 @@ class [[eosio::contract]] evm_contract : public contract void call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce); - // to allow sending through a Bytes (basic_string) w/o copying over to a std::vector - void pushtx_bytes(eosio::name miner, const std::basic_string& rlptx); - using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx_bytes>; + using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx>; - void pushtx( eosio::name miner, const bytes& rlptx, silkworm::Transaction& txn, bool is_from_self ); - void versioned_tx_dispatch(silkworm::Transaction& txn, uint64_t current_version); + void process_tx(const runtime_config& rc, eosio::name miner, transaction& tx); + void dispatch_tx(const runtime_config& rc, transaction& tx); }; diff --git a/include/evm_runtime/runtime_config.hpp b/include/evm_runtime/runtime_config.hpp new file mode 100644 index 00000000..404fec59 --- /dev/null +++ b/include/evm_runtime/runtime_config.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace evm_runtime { + +struct runtime_config { + bool allow_special_signature = false; + bool abort_on_failure = false; + bool enforce_chain_id = true; + bool allow_non_self_miner = true; +}; + +} //namespace evm_runtime \ No newline at end of file diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index 019e8fcb..6c53f1af 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include namespace evm_runtime { @@ -229,4 +231,67 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] config2 EOSLIB_SERIALIZE(config2, (next_account_id)); }; +struct evm_version_type { + struct pending { + uint64_t version; + time_point time; + + bool is_active(time_point_sec genesis_time, time_point current_time)const { + eosevm::block_mapping bm(genesis_time.sec_since_epoch()); + auto current_block_num = bm.timestamp_to_evm_block_num(current_time.time_since_epoch().count()); + auto pending_block_num = bm.timestamp_to_evm_block_num(time.time_since_epoch().count()); + return current_block_num > pending_block_num; + } + }; + + std::pair get_version(time_point_sec genesis_time, time_point current_time)const { + uint64_t current_version = cached_version; + bool promote_required = false; + if(pending_version.has_value() && pending_version->is_active(genesis_time, current_time)) { + current_version = pending_version->version; + promote_required = true; + } + return std::make_pair(current_version, promote_required); + } + + void promote_pending() { + eosio::check(pending_version.has_value(), "no pending version"); + cached_version = pending_version.value().version; + pending_version.reset(); + } + + std::optional pending_version; + uint64_t cached_version=0; +}; + +struct [[eosio::table]] [[eosio::contract("evm_contract")]] config +{ + unsigned_int version; // placeholder for future variant index + uint64_t chainid = 0; + time_point_sec genesis_time; + asset ingress_bridge_fee = asset(0, token_symbol); + uint64_t gas_price = 0; + uint32_t miner_cut = 0; + uint32_t status = 0; // <- bit mask values from status_flags + binary_extension evm_version; + + EOSLIB_SERIALIZE(config, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)(evm_version)); +}; + +struct fee_parameters +{ + std::optional gas_price; ///< Minimum gas price (in 10^-18 EOS, aka wei) that is enforced on all + ///< transactions. Required during initialization. + + std::optional miner_cut; ///< Percentage cut (maximum allowed value of 100,000 which equals 100%) of the + ///< gas fee collected for a transaction that is sent to the indicated miner of + ///< that transaction. Required during initialization. + + std::optional ingress_bridge_fee; ///< Fee (in EOS) deducted from ingress transfers of EOS across bridge. + ///< Symbol must be in EOS and quantity must be non-negative. If not + ///< provided during initialization, the default fee of 0 will be used. +}; + + + } //namespace evm_runtime diff --git a/include/evm_runtime/transaction.hpp b/include/evm_runtime/transaction.hpp new file mode 100644 index 00000000..ff0cb23c --- /dev/null +++ b/include/evm_runtime/transaction.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace evm_runtime { + +using silkworm::Bytes; +using silkworm::ByteView; + +struct transaction { + + transaction() = delete; + explicit transaction(bytes& rlptx) : rlptx_(std::move(rlptx)) {} + explicit transaction(silkworm::Transaction& tx) : tx_(std::move(tx)) {} + + const bytes& get_rlptx() { + if(!rlptx_) { + eosio::check(tx_.has_value(), "no tx"); + Bytes rlp; + silkworm::rlp::encode(rlp, tx_.value()); + rlptx_.emplace(bytes{rlp.begin(), rlp.end()}); + } + return rlptx_.value(); + } + + const silkworm::Transaction& get_tx() { + if(!tx_) { + eosio::check(rlptx_.has_value(), "no rlptx"); + ByteView bv{(const uint8_t*)rlptx_->data(), rlptx_->size()}; + silkworm::Transaction tmp; + eosio::check(silkworm::rlp::decode(bv, tmp) && bv.empty(), "unable to decode transaction"); + tx_.emplace(tmp); + } + return tx_.value(); + } + +private: + std::optional rlptx_; + std::optional tx_; +}; + +} //namespace evm_runtime \ No newline at end of file diff --git a/include/evm_runtime/types.hpp b/include/evm_runtime/types.hpp index b843b2b4..87f7fb68 100644 --- a/include/evm_runtime/types.hpp +++ b/include/evm_runtime/types.hpp @@ -12,6 +12,7 @@ namespace evm_runtime { using intx::operator""_u256; + static constexpr uint32_t hundred_percent = 100'000; constexpr unsigned evm_precision = 18; constexpr eosio::name token_account(eosio::name(TOKEN_ACCOUNT_NAME)); diff --git a/src/actions.cpp b/src/actions.cpp index 2ba1f3f9..8552a971 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -8,7 +8,6 @@ #include #include #include - #include // included here so NDEBUG is defined to disable assert macro #include @@ -30,40 +29,12 @@ namespace silkworm { return {}; } } - namespace evm_runtime { -static constexpr uint32_t hundred_percent = 100'000; static constexpr char err_msg_invalid_addr[] = "invalid address"; using namespace silkworm; -void set_fee_parameters(evm_contract::config& current_config, - const evm_contract::fee_parameters& fee_params, - bool allow_any_to_be_unspecified) -{ - if (fee_params.gas_price.has_value()) { - current_config.gas_price = *fee_params.gas_price; - } else { - check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing gas_price"); - } - - if (fee_params.miner_cut.has_value()) { - check(*fee_params.miner_cut <= hundred_percent, "miner_cut cannot exceed 100,000 (100%)"); - - current_config.miner_cut = *fee_params.miner_cut; - } else { - check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing miner_cut"); - } - - if (fee_params.ingress_bridge_fee.has_value()) { - check(fee_params.ingress_bridge_fee->symbol == token_symbol, "unexpected bridge symbol"); - check(fee_params.ingress_bridge_fee->amount >= 0, "ingress bridge fee cannot be negative"); - - current_config.ingress_bridge_fee = *fee_params.ingress_bridge_fee; - } -} - void evm_contract::init(const uint64_t chainid, const fee_parameters& fee_params) { eosio::require_auth(get_self()); @@ -74,22 +45,19 @@ void evm_contract::init(const uint64_t chainid, const fee_parameters& fee_params // Convert current time to EVM compatible block timestamp used as genesis time by rounding down to nearest second. time_point_sec genesis_time = eosio::current_time_point(); - config new_config = { - .version = 0, - .chainid = chainid, - .genesis_time = genesis_time, - }; + _config.set_version(0); + _config.set_chainid(chainid); + _config.set_genesis_time(genesis_time); - // Other fee parameters in new_config are still left at their (undesired) default values. - // Correct those values now using the fee_params passed in as an argument to the init function. + // Other fee parameters in new_config are still left at their (undesired) + // default values. Correct those values now using the fee_params passed in as + // an argument to the init function. - set_fee_parameters(new_config, fee_params, false); // enforce that all fee parameters are specified - - _config.set(new_config, get_self()); + _config.set_fee_parameters(fee_params, false); // enforce that all fee parameters are specified inevm_singleton(get_self(), get_self().value).get_or_create(get_self()); - open(get_self()); + open_internal_balance(get_self()); } void evm_contract::setfeeparams(const fee_parameters& fee_params) @@ -97,9 +65,9 @@ void evm_contract::setfeeparams(const fee_parameters& fee_params) assert_inited(); require_auth(get_self()); - config current_config = _config.get(); - set_fee_parameters(current_config, fee_params, true); // do not enforce that all fee parameters are specified - _config.set(current_config, get_self()); + _config.set_fee_parameters( + fee_params, + true); // do not enforce that all fee parameters are specified } void evm_contract::addegress(const std::vector& accounts) { @@ -130,13 +98,13 @@ void evm_contract::freeze(bool value) { eosio::require_auth(get_self()); assert_inited(); - config config2 = _config.get(); + auto status = _config.get_status(); if (value) { - config2.status |= static_cast(status_flags::frozen); + status |= static_cast(status_flags::frozen); } else { - config2.status &= ~static_cast(status_flags::frozen); + status &= ~static_cast(status_flags::frozen); } - _config.set(config2, get_self()); + _config.set_status(status); } void check_result( ValidationResult r, const Transaction& txn, const char* desc ) { @@ -189,10 +157,7 @@ void check_result( ValidationResult r, const Transaction& txn, const char* desc eosio::check( false, std::move(err_msg)); } -Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id, bool is_from_self) { - //when being called as an inline action, clutch in allowance for reserved addresses & signatures by setting from_self=true - const bool from_self = is_from_self || get_sender() == get_self(); - +Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Block& block, Transaction& tx, silkworm::ExecutionProcessor& ep) { balances balance_table(get_self(), get_self().value); if (miner == get_self()) { @@ -226,7 +191,7 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& // 2 For special signature, we will reject calls not from self // and process the special balance if the tx is from reserved address. if (is_special_signature) { - check(from_self, "bridge signature used outside of bridge transaction"); + check(rc.allow_special_signature, "bridge signature used outside of bridge transaction"); if (is_reserved_address(*tx.from)) { const name ingress_account(*extract_reserved_address(*tx.from)); @@ -245,9 +210,9 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& } } - // A tx from self with regular signature can potentially from external source. + // A tx from self with regular signature can potentially from external source. // Therefore, only tx with special signature can waive the chain_id check. - if(enforce_chain_id && !is_special_signature) { + if (rc.enforce_chain_id) { check(tx.chain_id.has_value(), "tx without chain-id"); } @@ -266,12 +231,12 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& uint64_t tx_gas_used = receipt.cumulative_gas_used; // Only transaction in the "block" so cumulative_gas_used is the tx gas_used. intx::uint512 gas_fee = intx::uint256(tx_gas_used) * tx.max_fee_per_gas; check(gas_fee < std::numeric_limits::max(), "too much gas"); - gas_fee *= _config.get().miner_cut; + gas_fee *= _config.get_miner_cut(); gas_fee /= hundred_percent; gas_fee_miner_portion.emplace(static_cast(gas_fee)); } - if(from_self) + if (rc.abort_on_failure) eosio::check(receipt.success, "tx executed inline by contract must succeed"); if(!ep.state().reserved_objects().empty()) { @@ -339,11 +304,10 @@ void evm_contract::exec(const exec_input& input, const std::optional> found_chain_config = lookup_known_chain(current_config.chainid); + std::optional> found_chain_config = lookup_known_chain(_config.get_chainid()); check( found_chain_config.has_value(), "failed to find expected chain config" ); - eosevm::block_mapping bm(current_config.genesis_time.sec_since_epoch()); + eosevm::block_mapping bm(_config.get_genesis_time().sec_since_epoch()); Block block; eosevm::prepare_block_header(block.header, bm, get_self().value, @@ -432,25 +396,23 @@ void evm_contract::process_filtered_messages(const std::vector> found_chain_config = lookup_known_chain(current_config.chainid); + std::optional> found_chain_config = lookup_known_chain(_config.get_chainid()); check( found_chain_config.has_value(), "failed to find expected chain config" ); - eosevm::block_mapping bm(current_config.genesis_time.sec_since_epoch()); + eosevm::block_mapping bm(_config.get_genesis_time().sec_since_epoch()); Block block; - eosevm::prepare_block_header(block.header, bm, get_self().value, - bm.timestamp_to_evm_block_num(current_time.time_since_epoch().count())); + eosevm::prepare_block_header(block.header, bm, get_self().value, + bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count())); silkworm::protocol::TrustRuleSet engine{*found_chain_config->second}; @@ -458,7 +420,7 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx, silkworm::Tran silkworm::ExecutionProcessor ep{block, engine, state, *found_chain_config->second}; check(tx.max_priority_fee_per_gas == tx.max_fee_per_gas, "max_priority_fee_per_gas must be equal to max_fee_per_gas"); - check(tx.max_fee_per_gas >= current_config.gas_price, "gas price is too low"); + check(tx.max_fee_per_gas >= _config.get_gas_price(), "gas price is too low"); // Filter EVM messages (with data) that are sent to the reserved address // corresponding to the EOS account holding the contract (self) @@ -467,38 +429,58 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx, silkworm::Tran return message.recipient == me && message.input_size > 0; }); - auto receipt = execute_tx(miner, block, tx, ep, true, is_from_self); + auto receipt = execute_tx(rc, miner, block, tx, ep); process_filtered_messages(ep.state().filtered_messages()); engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); - if( current_version >= 1 ) { - auto event = evmtx_type{evmtx_v0{current_version, rlptx}}; - action(std::vector{}, get_self(), "evmtx"_n, event).send(); - } - if( update_required ) { - eosio::check(current_config.evm_version.has_value(), "no evm_version"); - current_config.evm_version->promote_pending(); - _config.set(current_config, get_self()); + if (current_version >= 1) { + auto event = evmtx_type{evmtx_v0{current_version, txn.get_rlptx()}}; + action(std::vector{}, get_self(), "evmtx"_n, event) + .send(); } LOGTIME("EVM END"); } -void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { +void evm_contract::pushtx(eosio::name miner, bytes& rlptx) { LOGTIME("EVM START0"); + assert_unfrozen(); - Transaction tx; - ByteView bv{(const uint8_t*)rlptx.data(), rlptx.size()}; - eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); + // Use default runtime configuration parameters. + runtime_config rc; + + // Check if the transaction is initiated by the contract itself. + // When the contract calls this as an inline action, it implies a special + // context where certain rules are relaxed or altered. + // 1. Allowance for special signatures: This permits the use of special + // signatures that are otherwise restricted. + // 2. Enforce immediate EOS transaction failure on error: In case of failure + // in the EVM transaction execution, the EOS transaction is set to abort + // immediately. + // 3. Disable chainId enforcement: This is applicable because the RLP + // transaction sent by the contract is a legacy pre-EIP155 transaction that is + // not replay-protected (lacks a chain ID). + // 4. Restrict miner to self: Ensures that if the transaction originates from + // the contract then the miner must also be the contract itself. + if (get_sender() == get_self()) { + rc.allow_special_signature = true; + rc.abort_on_failure = true; + rc.enforce_chain_id = false; + rc.allow_non_self_miner = false; + } - pushtx(miner, rlptx, tx, false); + transaction tx(rlptx); + process_tx(rc, miner, tx); } void evm_contract::open(eosio::name owner) { assert_unfrozen(); require_auth(owner); + open_internal_balance(owner); +} +void evm_contract::open_internal_balance(eosio::name owner) { balances balance_table(get_self(), get_self().value); if(balance_table.find(owner.value) == balance_table.end()) balance_table.emplace(owner, [&](balance& a) { @@ -544,7 +526,6 @@ uint64_t evm_contract::get_and_increment_nonce(const name owner) { checksum256 evm_contract::get_code_hash(name account) const { char buff[64]; - eosio::check(internal_use_do_not_use::get_code_hash(account.value, 0, buff, sizeof(buff)) <= sizeof(buff), "get_code_hash() too big"); using start_of_code_hash_return = std::tuple; const auto& [v, s, code_hash] = unpack(buff, sizeof(buff)); @@ -570,12 +551,8 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& b.balance.balance += quantity; }); - auto current_config = _config.get(); - auto current_time = eosio::current_time_point(); - auto [current_version, update_required] = current_config.get_version(current_time); - //subtract off the ingress bridge fee from the quantity that will be bridged - quantity -= current_config.ingress_bridge_fee; + quantity -= _config.get_ingress_bridge_fee(); eosio::check(quantity.amount > 0, "must bridge more than ingress bridge fee"); const std::optional address_bytes = from_hex(memo); @@ -587,15 +564,22 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& Transaction txn; txn.type = TransactionType::kLegacy; txn.nonce = get_and_increment_nonce(get_self()); - txn.max_priority_fee_per_gas = current_config.gas_price; - txn.max_fee_per_gas = current_config.gas_price; + txn.max_priority_fee_per_gas = _config.get_gas_price(); + txn.max_fee_per_gas = _config.get_gas_price(); txn.gas_limit = 21000; txn.to = to_evmc_address(*address_bytes); txn.value = value; txn.r = 0u; // r == 0 is pseudo signature that resolves to reserved address range txn.s = get_self().value; - versioned_tx_dispatch(txn, current_version); + runtime_config rc; + rc.allow_special_signature = true; + rc.abort_on_failure = true; + rc.enforce_chain_id = false; + rc.allow_non_self_miner = false; + + transaction tx{txn}; + dispatch_tx(rc, tx); } void evm_contract::transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo) { @@ -640,15 +624,12 @@ bool evm_contract::gc(uint32_t max) { } void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce) { - auto current_config = _config.get(); - auto current_time = eosio::current_time_point(); - auto [current_version, update_required] = current_config.get_version(current_time); Transaction txn; txn.type = TransactionType::kLegacy; txn.nonce = nonce; - txn.max_priority_fee_per_gas = current_config.gas_price; - txn.max_fee_per_gas = current_config.gas_price; + txn.max_priority_fee_per_gas = _config.get_gas_price(); + txn.max_fee_per_gas = _config.get_gas_price(); txn.gas_limit = gas_limit; txn.value = value; txn.data = Bytes{(const uint8_t*)data.data(), data.size()}; @@ -661,18 +642,23 @@ void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, txn.to = to_evmc_address(bv_to); } - versioned_tx_dispatch(txn, current_version); + runtime_config rc; + rc.allow_special_signature = true; + rc.abort_on_failure = true; + rc.enforce_chain_id = false; + rc.allow_non_self_miner = false; + + transaction tx{txn}; + dispatch_tx(rc, tx); } -void evm_contract::versioned_tx_dispatch(silkworm::Transaction& txn, uint64_t current_version) { - Bytes rlp; - rlp::encode(rlp, txn); - if( current_version >= 1 ) { - auto rlptx = bytes{rlp.begin(), rlp.end()}; - pushtx(get_self(), rlptx, txn, true); +void evm_contract::dispatch_tx(const runtime_config& rc, transaction& tx) { + if (_config.get_evm_version() >= 1) { + process_tx(rc, get_self(), tx); } else { + eosio::check(rc.allow_special_signature && rc.abort_on_failure && !rc.enforce_chain_id && !rc.allow_non_self_miner, "invalid runtime config"); pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}}); - pushtx_act.send(get_self(), rlp); + pushtx_act.send(get_self(), tx.get_rlptx()); } } @@ -772,9 +758,7 @@ void evm_contract::assertnonce(eosio::name account, uint64_t next_nonce) { void evm_contract::setversion(uint64_t version) { require_auth(get_self()); - auto config = _config.get(); - config.set_version(version, eosio::current_block_time()); - _config.set(config, get_self()); + _config.set_evm_version(version); } } //evm_runtime diff --git a/src/test_actions.cpp b/src/test_actions.cpp index ba8bd5c6..ec0865a8 100644 --- a/src/test_actions.cpp +++ b/src/test_actions.cpp @@ -4,7 +4,7 @@ #include #include #include - +#include namespace evm_runtime { using namespace silkworm; @@ -25,7 +25,13 @@ using namespace silkworm; ByteView bv{(const uint8_t*)orlptx->data(), orlptx->size()}; eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); - execute_tx(eosio::name{}, block, tx, ep, false, false); + runtime_config rc { + .allow_special_signature = false, + .abort_on_failure = false, + .enforce_chain_id = false, + .allow_non_self_miner = true + }; + execute_tx(rc, eosio::name{}, block, tx, ep); } engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); From f0b6174703d286633b6dd7dc806be4684321a6b5 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 17 Jan 2024 03:07:25 -0300 Subject: [PATCH 11/23] bridge.hpp: removes unnecessary directives and includes --- include/evm_runtime/bridge.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp index c406aba8..d931cecd 100644 --- a/include/evm_runtime/bridge.hpp +++ b/include/evm_runtime/bridge.hpp @@ -1,13 +1,9 @@ #pragma once #include -#include namespace evm_runtime { namespace bridge { -using namespace std; -using namespace eosio; -using namespace silkworm; struct message_v0 { static constexpr uint32_t id = 0xf781185b; //sha3('bridgeMsgV0(string,bool,bytes)')[:4] From 7d42f76497528d583b17fffc2150bd2b1a7eef9a Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:00:32 -0300 Subject: [PATCH 12/23] Refactor config_wrapper --- include/evm_runtime/config_wrapper.hpp | 144 +++++++-------------- src/config_wrapper.cpp | 166 +++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 102 deletions(-) create mode 100644 src/config_wrapper.cpp diff --git a/include/evm_runtime/config_wrapper.hpp b/include/evm_runtime/config_wrapper.hpp index df32f152..9667e2cb 100644 --- a/include/evm_runtime/config_wrapper.hpp +++ b/include/evm_runtime/config_wrapper.hpp @@ -1,128 +1,68 @@ #pragma once #include -#include +#include +#include +#include namespace evm_runtime { using eosio::check; +struct fee_parameters; +struct config; + struct config_wrapper { - config_wrapper(eosio::name self) : _self(self), _config(self, self.value) { - _exists = _config.exists(); - if(_exists) { - _cached_config = _config.get(); - } - } - - ~config_wrapper() { - flush(); - } - - void flush() { - if( _evm_version_promote_required ) { - promote_pending_evm_version(); - set_dirty(); - } - if(!is_dirty()) { - return; - } - _config.set(_cached_config, _self); - clear_dirty(); - _exists = true; - } - - bool exists() { - return _exists; - } - - inline eosio::unsigned_int get_version()const { return _cached_config.version; } - void set_version(const eosio::unsigned_int version) { _cached_config.version = version; set_dirty(); } - - inline uint64_t get_chainid()const { return _cached_config.chainid; } - void set_chainid(uint64_t chainid) { _cached_config.chainid = chainid; set_dirty(); } - - inline const eosio::time_point_sec& get_genesis_time()const { return _cached_config.genesis_time; } - void set_genesis_time(eosio::time_point_sec genesis_time) { _cached_config.genesis_time = genesis_time; set_dirty(); } - - inline const eosio::asset& get_ingress_bridge_fee()const { return _cached_config.ingress_bridge_fee; } - void set_ingress_bridge_fee(const eosio::asset& ingress_bridge_fee) { _cached_config.ingress_bridge_fee = ingress_bridge_fee; set_dirty(); } - - inline uint64_t get_gas_price()const { return _cached_config.gas_price; } - void set_gas_price(uint64_t gas_price) { _cached_config.gas_price = gas_price; set_dirty(); } - - inline uint32_t get_miner_cut()const { return _cached_config.miner_cut; } - void set_miner_cut(uint32_t miner_cut) { _cached_config.miner_cut = miner_cut; set_dirty(); } - - inline uint32_t get_status()const { return _cached_config.status; } - void set_status(uint32_t status) { _cached_config.status = status; set_dirty(); } - - inline uint64_t get_evm_version()const { - uint64_t current_version = 0; - _evm_version_promote_required = false; - if(_cached_config.evm_version.has_value()) { - std::tie(current_version, _evm_version_promote_required) = _cached_config.evm_version->get_version(_cached_config.genesis_time, eosio::current_time_point()); - } - return current_version; - } - - void set_evm_version(uint64_t new_version) { - eosio::check(new_version <= MAX_EOSEVM_SUPPORTED_VERSION, "Unsupported version"); - auto current_version = get_evm_version(); - eosio::check(new_version > current_version, "new version must be greater than the active one"); - if( _evm_version_promote_required ) { - promote_pending_evm_version(); - } - auto current_time = eosio::current_time_point(); - _cached_config.evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, current_time}, current_version}); - set_dirty(); - } - - void promote_pending_evm_version() { - eosio::check(_cached_config.evm_version.has_value(), "no evm_version"); - _cached_config.evm_version->promote_pending(); - _evm_version_promote_required = false; - } + config_wrapper(eosio::name self); + ~config_wrapper(); - void set_fee_parameters(const fee_parameters& fee_params, - bool allow_any_to_be_unspecified) - { - if (fee_params.gas_price.has_value()) { - _cached_config.gas_price = *fee_params.gas_price; - } else { - check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing gas_price"); - } + void flush(); + bool exists(); + + eosio::unsigned_int get_version()const; + void set_version(const eosio::unsigned_int version); + + uint64_t get_chainid()const; + void set_chainid(uint64_t chainid); + + const eosio::time_point_sec& get_genesis_time()const; + void set_genesis_time(eosio::time_point_sec genesis_time); - if (fee_params.miner_cut.has_value()) { - check(*fee_params.miner_cut <= hundred_percent, "miner_cut cannot exceed 100,000 (100%)"); + const eosio::asset& get_ingress_bridge_fee()const; + void set_ingress_bridge_fee(const eosio::asset& ingress_bridge_fee); - _cached_config.miner_cut = *fee_params.miner_cut; - } else { - check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing miner_cut"); - } + uint64_t get_gas_price()const; + void set_gas_price(uint64_t gas_price); - if (fee_params.ingress_bridge_fee.has_value()) { - check(fee_params.ingress_bridge_fee->symbol == token_symbol, "unexpected bridge symbol"); - check(fee_params.ingress_bridge_fee->amount >= 0, "ingress bridge fee cannot be negative"); + uint32_t get_miner_cut()const; + void set_miner_cut(uint32_t miner_cut); - _cached_config.ingress_bridge_fee = *fee_params.ingress_bridge_fee; - } + uint32_t get_status()const; + void set_status(uint32_t status); - set_dirty(); - } + uint64_t get_evm_version()const; + + uint64_t get_evm_version_and_maybe_promote(); + + void set_evm_version(uint64_t new_version); + + void set_fee_parameters(const fee_parameters& fee_params, + bool allow_any_to_be_unspecified); private: - inline bool is_dirty()const { return _dirty; } - inline void set_dirty() { _dirty=true; } - inline void clear_dirty() { _dirty=false; } + bool is_dirty()const; + void set_dirty(); + void clear_dirty(); + + eosio::time_point get_current_time()const; bool _dirty = false; bool _exists = false; - mutable bool _evm_version_promote_required = false; - config _cached_config; + config* _cached_config; eosio::name _self; eosio::singleton<"config"_n, config> _config; + mutable std::optional current_time_point; }; } //namespace evm_runtime \ No newline at end of file diff --git a/src/config_wrapper.cpp b/src/config_wrapper.cpp new file mode 100644 index 00000000..df919c98 --- /dev/null +++ b/src/config_wrapper.cpp @@ -0,0 +1,166 @@ +#pragma once +#include +#include + +namespace evm_runtime { + +config_wrapper::config_wrapper(eosio::name self) : _self(self), _config(self, self.value), _cached_config(new config()) { + _exists = _config.exists(); + if(_exists) { + *_cached_config = _config.get(); + } +} + +config_wrapper::~config_wrapper() { + flush(); +} + +void config_wrapper::flush() { + if(!is_dirty()) { + return; + } + _config.set(*_cached_config, _self); + clear_dirty(); + _exists = true; +} + +bool config_wrapper::exists() { + return _exists; +} + +eosio::unsigned_int config_wrapper::get_version()const { + return _cached_config->version; +} + +void config_wrapper::set_version(const eosio::unsigned_int version) { + _cached_config->version = version; + set_dirty(); +} + +uint64_t config_wrapper::get_chainid()const { + return _cached_config->chainid; +} + +void config_wrapper::set_chainid(uint64_t chainid) { + _cached_config->chainid = chainid; + set_dirty(); +} + +const eosio::time_point_sec& config_wrapper::get_genesis_time()const { + return _cached_config->genesis_time; +} + +void config_wrapper::set_genesis_time(eosio::time_point_sec genesis_time) { + _cached_config->genesis_time = genesis_time; + set_dirty(); +} + +const eosio::asset& config_wrapper::get_ingress_bridge_fee()const { + return _cached_config->ingress_bridge_fee; +} + +void config_wrapper::set_ingress_bridge_fee(const eosio::asset& ingress_bridge_fee) { + _cached_config->ingress_bridge_fee = ingress_bridge_fee; + set_dirty(); +} + +uint64_t config_wrapper::get_gas_price()const { + return _cached_config->gas_price; +} + +void config_wrapper::set_gas_price(uint64_t gas_price) { + _cached_config->gas_price = gas_price; + set_dirty(); +} + +uint32_t config_wrapper::get_miner_cut()const { + return _cached_config->miner_cut; +} + +void config_wrapper::set_miner_cut(uint32_t miner_cut) { + _cached_config->miner_cut = miner_cut; + set_dirty(); +} + +uint32_t config_wrapper::get_status()const { + return _cached_config->status; +} + +void config_wrapper::set_status(uint32_t status) { + _cached_config->status = status; + set_dirty(); +} + +uint64_t config_wrapper::get_evm_version()const { + uint64_t current_version = 0; + if(_cached_config->evm_version.has_value()) { + current_version = _cached_config->evm_version->get_version(_cached_config->genesis_time, get_current_time()); + } + return current_version; +} + +uint64_t config_wrapper::get_evm_version_and_maybe_promote() { + uint64_t current_version = 0; + bool promoted = false; + if(_cached_config->evm_version.has_value()) { + std::tie(current_version, promoted) = _cached_config->evm_version->get_version_and_maybe_promote(_cached_config->genesis_time, get_current_time()); + } + if(promoted) set_dirty(); + return current_version; +} + +void config_wrapper::set_evm_version(uint64_t new_version) { + eosio::check(new_version <= eosevm::max_eos_evm_version, "Unsupported version"); + auto current_version = get_evm_version_and_maybe_promote(); + eosio::check(new_version > current_version, "new version must be greater than the active one"); + _cached_config->evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, get_current_time()}, current_version}); + set_dirty(); +} + +void config_wrapper::set_fee_parameters(const fee_parameters& fee_params, + bool allow_any_to_be_unspecified) +{ + if (fee_params.gas_price.has_value()) { + _cached_config->gas_price = *fee_params.gas_price; + } else { + eosio::check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing gas_price"); + } + + if (fee_params.miner_cut.has_value()) { + eosio::check(*fee_params.miner_cut <= hundred_percent, "miner_cut cannot exceed 100,000 (100%)"); + + _cached_config->miner_cut = *fee_params.miner_cut; + } else { + eosio::check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing miner_cut"); + } + + if (fee_params.ingress_bridge_fee.has_value()) { + eosio::check(fee_params.ingress_bridge_fee->symbol == token_symbol, "unexpected bridge symbol"); + eosio::check(fee_params.ingress_bridge_fee->amount >= 0, "ingress bridge fee cannot be negative"); + + _cached_config->ingress_bridge_fee = *fee_params.ingress_bridge_fee; + } + + set_dirty(); +} + +bool config_wrapper::is_dirty()const { + return _dirty; +} + +void config_wrapper::set_dirty() { + _dirty=true; +} + +void config_wrapper::clear_dirty() { + _dirty=false; +} + +time_point config_wrapper::get_current_time()const { + if(!current_time_point.has_value()) { + current_time_point = eosio::current_time_point(); + } + return current_time_point.value(); +} + +} //namespace evm_runtime \ No newline at end of file From 2df364a7edaf3abb95bdf37a4a4179fc232ff1df Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:01:27 -0300 Subject: [PATCH 13/23] Change kTestNetwork initialization to be compatible with the new ChainConfig --- include/evm_runtime/test/config.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/evm_runtime/test/config.hpp b/include/evm_runtime/test/config.hpp index 635533c9..bf8af1c0 100644 --- a/include/evm_runtime/test/config.hpp +++ b/include/evm_runtime/test/config.hpp @@ -7,14 +7,14 @@ namespace test { inline constexpr ChainConfig kTestNetwork{ .chain_id = 1, .protocol_rule_set = protocol::RuleSetType::kNoProof, - .homestead_block = 0, - .dao_block = 0, - .tangerine_whistle_block = 0, - .spurious_dragon_block = 0, - .byzantium_block = 0, - .constantinople_block = 0, - .petersburg_block = 0, - .istanbul_block = 0, + ._homestead_block = 0, + ._dao_block = 0, + ._tangerine_whistle_block = 0, + ._spurious_dragon_block = 0, + ._byzantium_block = 0, + ._constantinople_block = 0, + ._petersburg_block = 0, + ._istanbul_block = 0, }; } //namespace test From 547d8c7ad48bcde4e488eb4251c64ccaa139eb62 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:03:01 -0300 Subject: [PATCH 14/23] evm_contract: update function parameters --- include/evm_runtime/evm_contract.hpp | 20 +++-------- src/actions.cpp | 53 +++++++++++++++------------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 07a8c0b8..639570f5 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -8,7 +8,6 @@ #include #include #include - #include #include @@ -63,7 +62,7 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] void exec(const exec_input& input, const std::optional& callback); - [[eosio::action]] void pushtx(eosio::name miner, bytes& rlptx); + [[eosio::action]] void pushtx(eosio::name miner, bytes rlptx); [[eosio::action]] void open(eosio::name owner); @@ -147,24 +146,13 @@ class [[eosio::contract]] evm_contract : public contract void handle_account_transfer(const eosio::asset& quantity, const std::string& memo); void handle_evm_transfer(eosio::asset quantity, const std::string& memo); - void call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce); + void call_(const runtime_config& rc, intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce); using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx>; - void process_tx(const runtime_config& rc, eosio::name miner, transaction& tx); - void dispatch_tx(const runtime_config& rc, transaction& tx); + void process_tx(const runtime_config& rc, eosio::name miner, const transaction& tx); + void dispatch_tx(const runtime_config& rc, const transaction& tx); }; - } // namespace evm_runtime -namespace std { -template -DataStream& operator<<(DataStream& ds, const std::basic_string& bs) -{ - ds << (unsigned_int)bs.size(); - if (bs.size()) - ds.write((const char*)bs.data(), bs.size()); - return ds; -} -} // namespace std diff --git a/src/actions.cpp b/src/actions.cpp index 8552a971..9f71c090 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -311,7 +311,7 @@ void evm_contract::exec(const exec_input& input, const std::optional> found_chain_config = lookup_known_chain(_config.get_chainid()); check( found_chain_config.has_value(), "failed to find expected chain config" ); @@ -412,7 +412,7 @@ void evm_contract::process_tx(const runtime_config& rc, eosio::name miner, trans Block block; eosevm::prepare_block_header(block.header, bm, get_self().value, - bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count())); + bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count()), current_version); silkworm::protocol::TrustRuleSet engine{*found_chain_config->second}; @@ -443,7 +443,7 @@ void evm_contract::process_tx(const runtime_config& rc, eosio::name miner, trans LOGTIME("EVM END"); } -void evm_contract::pushtx(eosio::name miner, bytes& rlptx) { +void evm_contract::pushtx(eosio::name miner, bytes rlptx) { LOGTIME("EVM START0"); assert_unfrozen(); @@ -470,8 +470,7 @@ void evm_contract::pushtx(eosio::name miner, bytes& rlptx) { rc.allow_non_self_miner = false; } - transaction tx(rlptx); - process_tx(rc, miner, tx); + process_tx(rc, miner, transaction{std::move(rlptx)}); } void evm_contract::open(eosio::name owner) { @@ -578,8 +577,7 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& rc.enforce_chain_id = false; rc.allow_non_self_miner = false; - transaction tx{txn}; - dispatch_tx(rc, tx); + dispatch_tx(rc, transaction{std::move(txn)}); } void evm_contract::transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo) { @@ -623,7 +621,7 @@ bool evm_contract::gc(uint32_t max) { return state.gc(max); } -void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce) { +void evm_contract::call_(const runtime_config& rc, intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce) { Transaction txn; txn.type = TransactionType::kLegacy; @@ -642,18 +640,11 @@ void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, txn.to = to_evmc_address(bv_to); } - runtime_config rc; - rc.allow_special_signature = true; - rc.abort_on_failure = true; - rc.enforce_chain_id = false; - rc.allow_non_self_miner = false; - - transaction tx{txn}; - dispatch_tx(rc, tx); + dispatch_tx(rc, transaction{std::move(txn)}); } -void evm_contract::dispatch_tx(const runtime_config& rc, transaction& tx) { - if (_config.get_evm_version() >= 1) { +void evm_contract::dispatch_tx(const runtime_config& rc, const transaction& tx) { + if (_config.get_evm_version_and_maybe_promote() >= 1) { process_tx(rc, get_self(), tx); } else { eosio::check(rc.allow_special_signature && rc.abort_on_failure && !rc.enforce_chain_id && !rc.allow_non_self_miner, "invalid runtime config"); @@ -670,7 +661,14 @@ void evm_contract::call(eosio::name from, const bytes& to, const bytes& value, c eosio::check(value.size() == sizeof(intx::uint256), "invalid value"); intx::uint256 v = intx::be::unsafe::load((const uint8_t *)value.data()); - call_(from.value, to, v, data, gas_limit, get_and_increment_nonce(from)); + runtime_config rc { + .allow_special_signature = true, + .abort_on_failure = true, + .enforce_chain_id = false, + .allow_non_self_miner = false + }; + + call_(rc, from.value, to, v, data, gas_limit, get_and_increment_nonce(from)); } void evm_contract::admincall(const bytes& from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) { @@ -702,8 +700,15 @@ void evm_contract::admincall(const bytes& from, const bytes& to, const bytes& va check(!!account, err_msg_invalid_addr); nonce = account->nonce; } - - call_(s, to, v, data, gas_limit, nonce); + + runtime_config rc { + .allow_special_signature = true, + .abort_on_failure = true, + .enforce_chain_id = false, + .allow_non_self_miner = false + }; + + call_(rc, s, to, v, data, gas_limit, nonce); } void evm_contract::bridgereg(eosio::name receiver, eosio::name handler, const eosio::asset& min_fee) { From 2ef681f124ca07add48eb236cef213656894072d Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:03:48 -0300 Subject: [PATCH 15/23] test_engine: Use new ChainConfig revision interface --- include/evm_runtime/test/engine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/evm_runtime/test/engine.hpp b/include/evm_runtime/test/engine.hpp index 1d938319..866ccf59 100644 --- a/include/evm_runtime/test/engine.hpp +++ b/include/evm_runtime/test/engine.hpp @@ -19,7 +19,7 @@ class engine : public silkworm::protocol::IRuleSet { void finalize(IntraBlockState& state, const Block& block) override { intx::uint256 block_reward; - const evmc_revision revision{config.revision(block.header.number, block.header.timestamp)}; + const evmc_revision revision{config.revision(block.header)}; if (revision >= EVMC_CONSTANTINOPLE) { block_reward = silkworm::protocol::kBlockRewardConstantinople; } else if (revision >= EVMC_BYZANTIUM) { From 3974168f6d33f496de2db62f1a5ff3e30af4986c Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:04:58 -0300 Subject: [PATCH 16/23] refactor evm_runtime::transaction --- include/evm_runtime/transaction.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/evm_runtime/transaction.hpp b/include/evm_runtime/transaction.hpp index ff0cb23c..dee97342 100644 --- a/include/evm_runtime/transaction.hpp +++ b/include/evm_runtime/transaction.hpp @@ -14,10 +14,10 @@ using silkworm::ByteView; struct transaction { transaction() = delete; - explicit transaction(bytes& rlptx) : rlptx_(std::move(rlptx)) {} - explicit transaction(silkworm::Transaction& tx) : tx_(std::move(tx)) {} + explicit transaction(bytes rlptx) : rlptx_(std::move(rlptx)) {} + explicit transaction(silkworm::Transaction tx) : tx_(std::move(tx)) {} - const bytes& get_rlptx() { + const bytes& get_rlptx()const { if(!rlptx_) { eosio::check(tx_.has_value(), "no tx"); Bytes rlp; @@ -27,7 +27,7 @@ struct transaction { return rlptx_.value(); } - const silkworm::Transaction& get_tx() { + silkworm::Transaction& get_tx()const { if(!tx_) { eosio::check(rlptx_.has_value(), "no rlptx"); ByteView bv{(const uint8_t*)rlptx_->data(), rlptx_->size()}; @@ -39,8 +39,8 @@ struct transaction { } private: - std::optional rlptx_; - std::optional tx_; + mutable std::optional rlptx_; + mutable std::optional tx_; }; } //namespace evm_runtime \ No newline at end of file From 047c230220a90ba7fd62dc5eff6b471eb158fdea Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:05:21 -0300 Subject: [PATCH 17/23] Add config_wrapper.cpp to CMakeLists.txt --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bfee316e..8facc47e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/state.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/actions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/config_wrapper.cpp ) if (WITH_TEST_ACTIONS) add_compile_definitions(WITH_TEST_ACTIONS) From c0bd3d8914440df3676d1dabb5deee5eaa796c05 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:06:08 -0300 Subject: [PATCH 18/23] Refactor evm_version_type --- include/evm_runtime/tables.hpp | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index 6c53f1af..1be9088c 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -244,14 +244,23 @@ struct evm_version_type { } }; - std::pair get_version(time_point_sec genesis_time, time_point current_time)const { + uint64_t get_version(time_point_sec genesis_time, time_point current_time)const { uint64_t current_version = cached_version; - bool promote_required = false; if(pending_version.has_value() && pending_version->is_active(genesis_time, current_time)) { current_version = pending_version->version; - promote_required = true; } - return std::make_pair(current_version, promote_required); + return current_version; + } + + std::pair get_version_and_maybe_promote(time_point_sec genesis_time, time_point current_time) { + uint64_t current_version = cached_version; + bool promoted = false; + if(pending_version.has_value() && pending_version->is_active(genesis_time, current_time)) { + current_version = pending_version->version; + promote_pending(); + promoted = true; + } + return std::make_pair(current_version, promoted); } void promote_pending() { @@ -278,20 +287,4 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] config EOSLIB_SERIALIZE(config, (version)(chainid)(genesis_time)(ingress_bridge_fee)(gas_price)(miner_cut)(status)(evm_version)); }; -struct fee_parameters -{ - std::optional gas_price; ///< Minimum gas price (in 10^-18 EOS, aka wei) that is enforced on all - ///< transactions. Required during initialization. - - std::optional miner_cut; ///< Percentage cut (maximum allowed value of 100,000 which equals 100%) of the - ///< gas fee collected for a transaction that is sent to the indicated miner of - ///< that transaction. Required during initialization. - - std::optional ingress_bridge_fee; ///< Fee (in EOS) deducted from ingress transfers of EOS across bridge. - ///< Symbol must be in EOS and quantity must be non-negative. If not - ///< provided during initialization, the default fee of 0 will be used. -}; - - - } //namespace evm_runtime From a2e5dc626461506d199f3603dd9beb6bde21ccd4 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:06:34 -0300 Subject: [PATCH 19/23] Move fee_parameters into types.hpp --- include/evm_runtime/types.hpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/include/evm_runtime/types.hpp b/include/evm_runtime/types.hpp index 87f7fb68..cf52e68b 100644 --- a/include/evm_runtime/types.hpp +++ b/include/evm_runtime/types.hpp @@ -2,13 +2,13 @@ #include #include +#include #include #include #include #include #define TOKEN_ACCOUNT_NAME "eosio.token" -#define MAX_EOSEVM_SUPPORTED_VERSION 1 namespace evm_runtime { using intx::operator""_u256; @@ -86,6 +86,20 @@ namespace evm_runtime { using evmtx_type = std::variant; + struct fee_parameters + { + std::optional gas_price; ///< Minimum gas price (in 10^-18 EOS, aka wei) that is enforced on all + ///< transactions. Required during initialization. + + std::optional miner_cut; ///< Percentage cut (maximum allowed value of 100,000 which equals 100%) of the + ///< gas fee collected for a transaction that is sent to the indicated miner of + ///< that transaction. Required during initialization. + + std::optional ingress_bridge_fee; ///< Fee (in EOS) deducted from ingress transfers of EOS across bridge. + ///< Symbol must be in EOS and quantity must be non-negative. If not + ///< provided during initialization, the default fee of 0 will be used. + }; + } //namespace evm_runtime namespace eosio { @@ -98,4 +112,15 @@ inline datastream& operator>>(datastream& ds, evm_runtime::uint2 return ds; } -} //namespace eosio \ No newline at end of file +} //namespace eosio + +namespace std { +template +DataStream& operator<<(DataStream& ds, const std::basic_string& bs) +{ + ds << (eosio::unsigned_int)bs.size(); + if (bs.size()) + ds.write((const char*)bs.data(), bs.size()); + return ds; +} +} // namespace std \ No newline at end of file From cc1bb0bc6262421398e4981759cbb6024057e70f Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 00:07:12 -0300 Subject: [PATCH 20/23] Update silkworm --- silkworm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silkworm b/silkworm index 8a3b69ea..eb305ece 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 8a3b69ea81ff567dadbf98db4f86652782acd6b4 +Subproject commit eb305eced3dcbec3abc04d199224146aef21cad1 From 8cad78ab8e6f064614f475a41e7ab67f1945652a Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 01:37:12 -0300 Subject: [PATCH 21/23] Add recover_sender in evm_runtime::transaction --- include/evm_runtime/evm_contract.hpp | 2 +- include/evm_runtime/transaction.hpp | 9 ++++++++- src/actions.cpp | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 639570f5..34741cf9 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -136,7 +136,7 @@ class [[eosio::contract]] evm_contract : public contract check((_config.get_status() & static_cast(status_flags::frozen)) == 0, "contract is frozen"); } - silkworm::Receipt execute_tx(const runtime_config& rc, eosio::name miner, silkworm::Block& block, silkworm::Transaction& tx, silkworm::ExecutionProcessor& ep); + silkworm::Receipt execute_tx(const runtime_config& rc, eosio::name miner, silkworm::Block& block, const transaction& tx, silkworm::ExecutionProcessor& ep); void process_filtered_messages(const std::vector& filtered_messages); uint64_t get_and_increment_nonce(const name owner); diff --git a/include/evm_runtime/transaction.hpp b/include/evm_runtime/transaction.hpp index dee97342..6e21932b 100644 --- a/include/evm_runtime/transaction.hpp +++ b/include/evm_runtime/transaction.hpp @@ -27,7 +27,7 @@ struct transaction { return rlptx_.value(); } - silkworm::Transaction& get_tx()const { + const silkworm::Transaction& get_tx()const { if(!tx_) { eosio::check(rlptx_.has_value(), "no rlptx"); ByteView bv{(const uint8_t*)rlptx_->data(), rlptx_->size()}; @@ -38,6 +38,13 @@ struct transaction { return tx_.value(); } + void recover_sender()const { + eosio::check(tx_.has_value(), "no tx"); + auto& tx = tx_.value(); + tx.from.reset(); + tx.recover_sender(); + } + private: mutable std::optional rlptx_; mutable std::optional tx_; diff --git a/src/actions.cpp b/src/actions.cpp index 9f71c090..bfcefb1f 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -157,7 +157,8 @@ void check_result( ValidationResult r, const Transaction& txn, const char* desc eosio::check( false, std::move(err_msg)); } -Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Block& block, Transaction& tx, silkworm::ExecutionProcessor& ep) { +Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Block& block, const transaction& txn, silkworm::ExecutionProcessor& ep) { + const auto& tx = txn.get_tx(); balances balance_table(get_self(), get_self().value); if (miner == get_self()) { @@ -181,8 +182,7 @@ Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Bl bool is_special_signature = silkworm::is_special_signature(tx.r, tx.s); - tx.from.reset(); - tx.recover_sender(); + txn.recover_sender(); eosio::check(tx.from.has_value(), "unable to recover sender"); LOGTIME("EVM RECOVER SENDER"); From e20075afab17e50afc182eaf1cee22fa4eaf136e Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 01:42:25 -0300 Subject: [PATCH 22/23] replace config struct raw pointer inside configwrapper, with a smart pointer for configwrapper in the evm_contract --- include/evm_runtime/config_wrapper.hpp | 10 +--- include/evm_runtime/evm_contract.hpp | 19 ++------ src/actions.cpp | 67 ++++++++++++++++---------- src/config_wrapper.cpp | 50 +++++++++---------- 4 files changed, 73 insertions(+), 73 deletions(-) diff --git a/include/evm_runtime/config_wrapper.hpp b/include/evm_runtime/config_wrapper.hpp index 9667e2cb..c109bafb 100644 --- a/include/evm_runtime/config_wrapper.hpp +++ b/include/evm_runtime/config_wrapper.hpp @@ -3,14 +3,10 @@ #include #include #include - +#include namespace evm_runtime { -using eosio::check; - struct fee_parameters; -struct config; - struct config_wrapper { config_wrapper(eosio::name self); @@ -41,9 +37,7 @@ struct config_wrapper { void set_status(uint32_t status); uint64_t get_evm_version()const; - uint64_t get_evm_version_and_maybe_promote(); - void set_evm_version(uint64_t new_version); void set_fee_parameters(const fee_parameters& fee_params, @@ -58,7 +52,7 @@ struct config_wrapper { bool _dirty = false; bool _exists = false; - config* _cached_config; + config _cached_config; eosio::name _self; eosio::singleton<"config"_n, config> _config; diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 34741cf9..6ef51f72 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -23,8 +22,7 @@ class [[eosio::contract]] evm_contract : public contract { public: using contract::contract; - evm_contract(eosio::name receiver, eosio::name code, const datastream& ds) : contract(receiver, code, ds), _config(get_self()) {} - + evm_contract(eosio::name receiver, eosio::name code, const datastream& ds); /** * @brief Initialize EVM contract @@ -117,24 +115,15 @@ class [[eosio::contract]] evm_contract : public contract private: void open_internal_balance(eosio::name owner); - config_wrapper _config; + std::shared_ptr _config; enum class status_flags : uint32_t { frozen = 0x1 }; - void assert_inited() - { - check(_config.exists(), "contract not initialized"); - check(_config.get_version() == 0u, "unsupported configuration singleton"); - } - - void assert_unfrozen() - { - assert_inited(); - check((_config.get_status() & static_cast(status_flags::frozen)) == 0, "contract is frozen"); - } + void assert_inited(); + void assert_unfrozen(); silkworm::Receipt execute_tx(const runtime_config& rc, eosio::name miner, silkworm::Block& block, const transaction& tx, silkworm::ExecutionProcessor& ep); void process_filtered_messages(const std::vector& filtered_messages); diff --git a/src/actions.cpp b/src/actions.cpp index bfcefb1f..158d3c12 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -8,6 +8,8 @@ #include #include #include +#include + #include // included here so NDEBUG is defined to disable assert macro #include @@ -35,25 +37,40 @@ static constexpr char err_msg_invalid_addr[] = "invalid address"; using namespace silkworm; +evm_contract::evm_contract(eosio::name receiver, eosio::name code, const datastream& ds) : + contract(receiver, code, ds), _config(std::make_shared(get_self())) {} + +inline void evm_contract::assert_inited() +{ + check(_config->exists(), "contract not initialized"); + check(_config->get_version() == 0u, "unsupported configuration singleton"); +} + +inline void evm_contract::assert_unfrozen() +{ + assert_inited(); + check((_config->get_status() & static_cast(status_flags::frozen)) == 0, "contract is frozen"); +} + void evm_contract::init(const uint64_t chainid, const fee_parameters& fee_params) { eosio::require_auth(get_self()); - check(!_config.exists(), "contract already initialized"); + check(!_config->exists(), "contract already initialized"); check(!!lookup_known_chain(chainid), "unknown chainid"); // Convert current time to EVM compatible block timestamp used as genesis time by rounding down to nearest second. time_point_sec genesis_time = eosio::current_time_point(); - _config.set_version(0); - _config.set_chainid(chainid); - _config.set_genesis_time(genesis_time); + _config->set_version(0); + _config->set_chainid(chainid); + _config->set_genesis_time(genesis_time); // Other fee parameters in new_config are still left at their (undesired) // default values. Correct those values now using the fee_params passed in as // an argument to the init function. - _config.set_fee_parameters(fee_params, false); // enforce that all fee parameters are specified + _config->set_fee_parameters(fee_params, false); // enforce that all fee parameters are specified inevm_singleton(get_self(), get_self().value).get_or_create(get_self()); @@ -65,7 +82,7 @@ void evm_contract::setfeeparams(const fee_parameters& fee_params) assert_inited(); require_auth(get_self()); - _config.set_fee_parameters( + _config->set_fee_parameters( fee_params, true); // do not enforce that all fee parameters are specified } @@ -98,13 +115,13 @@ void evm_contract::freeze(bool value) { eosio::require_auth(get_self()); assert_inited(); - auto status = _config.get_status(); + auto status = _config->get_status(); if (value) { status |= static_cast(status_flags::frozen); } else { status &= ~static_cast(status_flags::frozen); } - _config.set_status(status); + _config->set_status(status); } void check_result( ValidationResult r, const Transaction& txn, const char* desc ) { @@ -231,7 +248,7 @@ Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Bl uint64_t tx_gas_used = receipt.cumulative_gas_used; // Only transaction in the "block" so cumulative_gas_used is the tx gas_used. intx::uint512 gas_fee = intx::uint256(tx_gas_used) * tx.max_fee_per_gas; check(gas_fee < std::numeric_limits::max(), "too much gas"); - gas_fee *= _config.get_miner_cut(); + gas_fee *= _config->get_miner_cut(); gas_fee /= hundred_percent; gas_fee_miner_portion.emplace(static_cast(gas_fee)); } @@ -304,14 +321,14 @@ void evm_contract::exec(const exec_input& input, const std::optional> found_chain_config = lookup_known_chain(_config.get_chainid()); + std::optional> found_chain_config = lookup_known_chain(_config->get_chainid()); check( found_chain_config.has_value(), "failed to find expected chain config" ); - eosevm::block_mapping bm(_config.get_genesis_time().sec_since_epoch()); + eosevm::block_mapping bm(_config->get_genesis_time().sec_since_epoch()); Block block; eosevm::prepare_block_header(block.header, bm, get_self().value, - bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count()), _config.get_evm_version()); + bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count()), _config->get_evm_version()); evm_runtime::state state{get_self(), get_self(), true}; IntraBlockState ibstate{state}; @@ -399,16 +416,16 @@ void evm_contract::process_filtered_messages(const std::vectorget_evm_version_and_maybe_promote(); - std::optional> found_chain_config = lookup_known_chain(_config.get_chainid()); + std::optional> found_chain_config = lookup_known_chain(_config->get_chainid()); check( found_chain_config.has_value(), "failed to find expected chain config" ); - eosevm::block_mapping bm(_config.get_genesis_time().sec_since_epoch()); + eosevm::block_mapping bm(_config->get_genesis_time().sec_since_epoch()); Block block; eosevm::prepare_block_header(block.header, bm, get_self().value, @@ -420,7 +437,7 @@ void evm_contract::process_tx(const runtime_config& rc, eosio::name miner, const silkworm::ExecutionProcessor ep{block, engine, state, *found_chain_config->second}; check(tx.max_priority_fee_per_gas == tx.max_fee_per_gas, "max_priority_fee_per_gas must be equal to max_fee_per_gas"); - check(tx.max_fee_per_gas >= _config.get_gas_price(), "gas price is too low"); + check(tx.max_fee_per_gas >= _config->get_gas_price(), "gas price is too low"); // Filter EVM messages (with data) that are sent to the reserved address // corresponding to the EOS account holding the contract (self) @@ -429,7 +446,7 @@ void evm_contract::process_tx(const runtime_config& rc, eosio::name miner, const return message.recipient == me && message.input_size > 0; }); - auto receipt = execute_tx(rc, miner, block, tx, ep); + auto receipt = execute_tx(rc, miner, block, txn, ep); process_filtered_messages(ep.state().filtered_messages()); @@ -551,7 +568,7 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& }); //subtract off the ingress bridge fee from the quantity that will be bridged - quantity -= _config.get_ingress_bridge_fee(); + quantity -= _config->get_ingress_bridge_fee(); eosio::check(quantity.amount > 0, "must bridge more than ingress bridge fee"); const std::optional address_bytes = from_hex(memo); @@ -563,8 +580,8 @@ void evm_contract::handle_evm_transfer(eosio::asset quantity, const std::string& Transaction txn; txn.type = TransactionType::kLegacy; txn.nonce = get_and_increment_nonce(get_self()); - txn.max_priority_fee_per_gas = _config.get_gas_price(); - txn.max_fee_per_gas = _config.get_gas_price(); + txn.max_priority_fee_per_gas = _config->get_gas_price(); + txn.max_fee_per_gas = _config->get_gas_price(); txn.gas_limit = 21000; txn.to = to_evmc_address(*address_bytes); txn.value = value; @@ -626,8 +643,8 @@ void evm_contract::call_(const runtime_config& rc, intx::uint256 s, const bytes& Transaction txn; txn.type = TransactionType::kLegacy; txn.nonce = nonce; - txn.max_priority_fee_per_gas = _config.get_gas_price(); - txn.max_fee_per_gas = _config.get_gas_price(); + txn.max_priority_fee_per_gas = _config->get_gas_price(); + txn.max_fee_per_gas = _config->get_gas_price(); txn.gas_limit = gas_limit; txn.value = value; txn.data = Bytes{(const uint8_t*)data.data(), data.size()}; @@ -644,7 +661,7 @@ void evm_contract::call_(const runtime_config& rc, intx::uint256 s, const bytes& } void evm_contract::dispatch_tx(const runtime_config& rc, const transaction& tx) { - if (_config.get_evm_version_and_maybe_promote() >= 1) { + if (_config->get_evm_version_and_maybe_promote() >= 1) { process_tx(rc, get_self(), tx); } else { eosio::check(rc.allow_special_signature && rc.abort_on_failure && !rc.enforce_chain_id && !rc.allow_non_self_miner, "invalid runtime config"); @@ -763,7 +780,7 @@ void evm_contract::assertnonce(eosio::name account, uint64_t next_nonce) { void evm_contract::setversion(uint64_t version) { require_auth(get_self()); - _config.set_evm_version(version); + _config->set_evm_version(version); } } //evm_runtime diff --git a/src/config_wrapper.cpp b/src/config_wrapper.cpp index df919c98..c6656176 100644 --- a/src/config_wrapper.cpp +++ b/src/config_wrapper.cpp @@ -4,10 +4,10 @@ namespace evm_runtime { -config_wrapper::config_wrapper(eosio::name self) : _self(self), _config(self, self.value), _cached_config(new config()) { +config_wrapper::config_wrapper(eosio::name self) : _self(self), _config(self, self.value) { _exists = _config.exists(); if(_exists) { - *_cached_config = _config.get(); + _cached_config = _config.get(); } } @@ -19,7 +19,7 @@ void config_wrapper::flush() { if(!is_dirty()) { return; } - _config.set(*_cached_config, _self); + _config.set(_cached_config, _self); clear_dirty(); _exists = true; } @@ -29,72 +29,72 @@ bool config_wrapper::exists() { } eosio::unsigned_int config_wrapper::get_version()const { - return _cached_config->version; + return _cached_config.version; } void config_wrapper::set_version(const eosio::unsigned_int version) { - _cached_config->version = version; + _cached_config.version = version; set_dirty(); } uint64_t config_wrapper::get_chainid()const { - return _cached_config->chainid; + return _cached_config.chainid; } void config_wrapper::set_chainid(uint64_t chainid) { - _cached_config->chainid = chainid; + _cached_config.chainid = chainid; set_dirty(); } const eosio::time_point_sec& config_wrapper::get_genesis_time()const { - return _cached_config->genesis_time; + return _cached_config.genesis_time; } void config_wrapper::set_genesis_time(eosio::time_point_sec genesis_time) { - _cached_config->genesis_time = genesis_time; + _cached_config.genesis_time = genesis_time; set_dirty(); } const eosio::asset& config_wrapper::get_ingress_bridge_fee()const { - return _cached_config->ingress_bridge_fee; + return _cached_config.ingress_bridge_fee; } void config_wrapper::set_ingress_bridge_fee(const eosio::asset& ingress_bridge_fee) { - _cached_config->ingress_bridge_fee = ingress_bridge_fee; + _cached_config.ingress_bridge_fee = ingress_bridge_fee; set_dirty(); } uint64_t config_wrapper::get_gas_price()const { - return _cached_config->gas_price; + return _cached_config.gas_price; } void config_wrapper::set_gas_price(uint64_t gas_price) { - _cached_config->gas_price = gas_price; + _cached_config.gas_price = gas_price; set_dirty(); } uint32_t config_wrapper::get_miner_cut()const { - return _cached_config->miner_cut; + return _cached_config.miner_cut; } void config_wrapper::set_miner_cut(uint32_t miner_cut) { - _cached_config->miner_cut = miner_cut; + _cached_config.miner_cut = miner_cut; set_dirty(); } uint32_t config_wrapper::get_status()const { - return _cached_config->status; + return _cached_config.status; } void config_wrapper::set_status(uint32_t status) { - _cached_config->status = status; + _cached_config.status = status; set_dirty(); } uint64_t config_wrapper::get_evm_version()const { uint64_t current_version = 0; - if(_cached_config->evm_version.has_value()) { - current_version = _cached_config->evm_version->get_version(_cached_config->genesis_time, get_current_time()); + if(_cached_config.evm_version.has_value()) { + current_version = _cached_config.evm_version->get_version(_cached_config.genesis_time, get_current_time()); } return current_version; } @@ -102,8 +102,8 @@ uint64_t config_wrapper::get_evm_version()const { uint64_t config_wrapper::get_evm_version_and_maybe_promote() { uint64_t current_version = 0; bool promoted = false; - if(_cached_config->evm_version.has_value()) { - std::tie(current_version, promoted) = _cached_config->evm_version->get_version_and_maybe_promote(_cached_config->genesis_time, get_current_time()); + if(_cached_config.evm_version.has_value()) { + std::tie(current_version, promoted) = _cached_config.evm_version->get_version_and_maybe_promote(_cached_config.genesis_time, get_current_time()); } if(promoted) set_dirty(); return current_version; @@ -113,7 +113,7 @@ void config_wrapper::set_evm_version(uint64_t new_version) { eosio::check(new_version <= eosevm::max_eos_evm_version, "Unsupported version"); auto current_version = get_evm_version_and_maybe_promote(); eosio::check(new_version > current_version, "new version must be greater than the active one"); - _cached_config->evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, get_current_time()}, current_version}); + _cached_config.evm_version.emplace(evm_version_type{evm_version_type::pending{new_version, get_current_time()}, current_version}); set_dirty(); } @@ -121,7 +121,7 @@ void config_wrapper::set_fee_parameters(const fee_parameters& fee_params, bool allow_any_to_be_unspecified) { if (fee_params.gas_price.has_value()) { - _cached_config->gas_price = *fee_params.gas_price; + _cached_config.gas_price = *fee_params.gas_price; } else { eosio::check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing gas_price"); } @@ -129,7 +129,7 @@ void config_wrapper::set_fee_parameters(const fee_parameters& fee_params, if (fee_params.miner_cut.has_value()) { eosio::check(*fee_params.miner_cut <= hundred_percent, "miner_cut cannot exceed 100,000 (100%)"); - _cached_config->miner_cut = *fee_params.miner_cut; + _cached_config.miner_cut = *fee_params.miner_cut; } else { eosio::check(allow_any_to_be_unspecified, "All required fee parameters not specified: missing miner_cut"); } @@ -138,7 +138,7 @@ void config_wrapper::set_fee_parameters(const fee_parameters& fee_params, eosio::check(fee_params.ingress_bridge_fee->symbol == token_symbol, "unexpected bridge symbol"); eosio::check(fee_params.ingress_bridge_fee->amount >= 0, "ingress bridge fee cannot be negative"); - _cached_config->ingress_bridge_fee = *fee_params.ingress_bridge_fee; + _cached_config.ingress_bridge_fee = *fee_params.ingress_bridge_fee; } set_dirty(); From a248501e2ed568ee061d877a0a3bf2688f493eeb Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 19 Jan 2024 01:55:48 -0300 Subject: [PATCH 23/23] Fix test_actions.cpp --- src/actions.cpp | 4 ++-- src/test_actions.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 158d3c12..91872256 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -40,13 +40,13 @@ using namespace silkworm; evm_contract::evm_contract(eosio::name receiver, eosio::name code, const datastream& ds) : contract(receiver, code, ds), _config(std::make_shared(get_self())) {} -inline void evm_contract::assert_inited() +void evm_contract::assert_inited() { check(_config->exists(), "contract not initialized"); check(_config->get_version() == 0u, "unsupported configuration singleton"); } -inline void evm_contract::assert_unfrozen() +void evm_contract::assert_unfrozen() { assert_inited(); check((_config->get_status() & static_cast(status_flags::frozen)) == 0, "contract is frozen"); diff --git a/src/test_actions.cpp b/src/test_actions.cpp index ec0865a8..8f36d9c1 100644 --- a/src/test_actions.cpp +++ b/src/test_actions.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace evm_runtime { using namespace silkworm; @@ -31,7 +32,7 @@ using namespace silkworm; .enforce_chain_id = false, .allow_non_self_miner = true }; - execute_tx(rc, eosio::name{}, block, tx, ep); + execute_tx(rc, eosio::name{}, block, transaction{std::move(tx)}, ep); } engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number);