From c092f8afb50ff515704880f6fc288167a5a0d3c7 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Mon, 24 Jul 2023 12:09:06 -0300 Subject: [PATCH 01/26] Add bridgereg/bridgeunreg actions --- include/evm_runtime/evm_contract.hpp | 3 +++ include/evm_runtime/tables.hpp | 11 +++++++++ src/actions.cpp | 36 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 81b47620..4259eff4 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -84,6 +84,9 @@ class [[eosio::contract]] evm_contract : public contract /// @return true if all garbage has been collected [[eosio::action]] bool gc(uint32_t max); + [[eosio::action]] void bridgereg(eosio::name receiver, const eosio::asset& min_fee); + [[eosio::action]] void bridgeunreg(eosio::name receiver); + #ifdef WITH_TEST_ACTIONS [[eosio::action]] void testtx(const std::optional& orlptx, const evm_runtime::test::block_info& bi); [[eosio::action]] void diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index 205a2d4c..174f9bb4 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -182,4 +182,15 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] allowed_egress_accou typedef eosio::multi_index<"egresslist"_n, allowed_egress_account> egresslist; +struct [[eosio::table]] [[eosio::contract("evm_contract")]] message_receiver { + name account; + asset min_fee; + + uint64_t primary_key() const { return account.value; } + + EOSLIB_SERIALIZE(message_receiver, (account)(min_fee)); +}; + +typedef eosio::multi_index<"msgreceiver"_n, message_receiver> message_receiver_table; + } //namespace evm_runtime \ No newline at end of file diff --git a/src/actions.cpp b/src/actions.cpp index 893aa4ee..99e814eb 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -555,6 +555,42 @@ bool evm_contract::gc(uint32_t max) { return state.gc(max); } +void evm_contract::bridgereg(eosio::name receiver, const eosio::asset& min_fee) { + assert_unfrozen(); + require_auth(receiver); + require_auth(get_self()); // to temporarily prevent registration of unauthorized accounts + + eosio::check(min_fee.symbol == token_symbol, "unexpected symbol"); + eosio::check(min_fee.amount >= 0, "min_fee cannot be negative"); + + auto update_row = [&](auto& row) { + row.account = receiver; + row.min_fee = min_fee; + }; + + message_receiver_table message_receivers(get_self(), get_self().value); + auto it = message_receivers.find(receiver.value); + + if(it == message_receivers.end()) { + message_receivers.emplace(receiver, update_row); + } else { + message_receivers.modify(*it, eosio::same_payer, update_row); + } + + open(receiver); +} + +void evm_contract::bridgeunreg(eosio::name receiver) { + assert_unfrozen(); + require_auth(receiver); + + message_receiver_table message_receivers(get_self(), get_self().value); + auto it = message_receivers.find(receiver.value); + eosio::check(it != message_receivers.end(), "receiver not found"); + message_receivers.erase(*it); +} + + #ifdef WITH_TEST_ACTIONS [[eosio::action]] void evm_contract::testtx( const std::optional& orlptx, const evm_runtime::test::block_info& bi ) { assert_unfrozen(); From 91c58a5fe8f60c1d85112e799b292add7718e6b9 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Mon, 24 Jul 2023 12:26:08 -0300 Subject: [PATCH 02/26] Handle 'emit' EVM call messages --- include/evm_runtime/bridge.hpp | 75 ++++++++++++++++++++++++++++++++++ include/evm_runtime/types.hpp | 12 +++++- silkworm | 2 +- src/CMakeLists.txt | 2 +- src/actions.cpp | 54 ++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 include/evm_runtime/bridge.hpp diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp new file mode 100644 index 00000000..28489a74 --- /dev/null +++ b/include/evm_runtime/bridge.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include + +namespace evm_runtime { namespace bridge { + +struct emit { + static constexpr uint32_t id = 0x7e95a247; //sha3('emit_(string,bytes)')[:4] + + string account; + bytes data; + + name get_account_as_name() const { + if(!account_.has_value()) { + account_ = name{account}; + eosio::check(account_.value().to_string() == account, "invalid account name"); + } + return *account_; + } + + eosio::time_point timestamp; + evmc_message message; + mutable std::optional account_; +}; + +using call = std::variant; + +std::optional decode_emit_message(eosio::datastream& ds) { + // offset_p1 (32) + offset_p2 (32) + // p1_len (32) + p1_data ((p1_len+31)/32*32) + // p2_len (32) + p1_data ((p2_len+31)/32*32) + uint256 offset_p1, offset_p2; + uint32_t p1_len, p2_len; + + ds >> offset_p1; + eosio::check(offset_p1 == 0x40, "invalid p1 offset"); + ds >> offset_p2; + eosio::check(offset_p2 == 0x80, "invalid p2 offset"); + + emit res; + + auto get_length=[&]() -> uint32_t { + uint256 len; + ds >> len; + eosio::check(len < std::numeric_limits::max(), "invalid length"); + return static_cast(len); + }; + + p1_len = get_length(); + auto p1_len_32 = (p1_len+31)/32*32; + res.account.resize(p1_len_32); + ds.read(res.account.data(), p1_len_32); + res.account.resize(p1_len); + + p2_len = get_length(); + auto p2_len_32 = (p2_len+31)/32*32; + res.data.resize(p2_len_32); + ds.read(res.data.data(), p2_len_32); + res.data.resize(p2_len); + + return res; +} + +std::optional decode_call_message(ByteView bv) { + // method_id (4) + eosio::datastream ds(bv.data(), bv.size()); + uint32_t method_id; + ds >> method_id; + + if(method_id == __builtin_bswap32(emit::id)) return decode_emit_message(ds); + return {}; +} + +} //namespace bridge +} //namespace evm_runtime diff --git a/include/evm_runtime/types.hpp b/include/evm_runtime/types.hpp index aa050640..09605bff 100644 --- a/include/evm_runtime/types.hpp +++ b/include/evm_runtime/types.hpp @@ -29,7 +29,7 @@ namespace evm_runtime { eosio::checksum256 make_key(bytes data); eosio::checksum256 make_key(const evmc::address& addr); eosio::checksum256 make_key(const evmc::bytes32& data); - + bytes to_bytes(const uint256& val); bytes to_bytes(const evmc::bytes32& val); bytes to_bytes(const evmc::address& addr); @@ -63,6 +63,16 @@ namespace evm_runtime { EOSLIB_SERIALIZE(exec_output, (status)(data)(context)); }; + struct bridge_emit_message { + eosio::name receiver; + bytes sender; + eosio::time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(bridge_emit_message, (receiver)(sender)(timestamp)(value)(data)); + }; + } //namespace evm_runtime namespace eosio { diff --git a/silkworm b/silkworm index 2f000a5c..df5d9ca3 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 2f000a5c5ae74da14e97333ef37bae8b059c63ae +Subproject commit df5d9ca396b4a85ace6b85aa16bbb0e5e0f90e50 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32f09944..dc6bb7da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,5 +85,5 @@ target_compile_options(evm_runtime PUBLIC --no-missing-ricardian-clause) if (WITH_LARGE_STACK) target_link_options(evm_runtime PUBLIC --stack-size=50000000) else() - target_link_options(evm_runtime PUBLIC --stack-size=38560) + target_link_options(evm_runtime PUBLIC --stack-size=32768) endif() diff --git a/src/actions.cpp b/src/actions.cpp index 99e814eb..e1b9eddb 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -406,8 +407,61 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { 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"); + // handler for all EVM call messages + std::vector emit_messages; + ep.set_evm_call_hook([&](const evmc_message& message, const evmc::Result& result){ + static auto me = make_reserved_address(get_self().value); + if (message.recipient != me || message.input_size == 0) return; + + eosio::check(result.status_code == EVMC_SUCCESS, "error calling reserved address"); + + auto call = bridge::decode_call_message(ByteView{message.input_data, message.input_size}); + eosio::check(call.has_value(), "unable to decode call message"); + + auto& emit = std::get(call.value()); + emit.message = message; + emit.timestamp = eosio::current_time_point(); + + const auto& receiver = emit.get_account_as_name(); + eosio::check(eosio::is_account(receiver), "receiver is not account"); + + message_receiver_table message_receivers(get_self(), get_self().value); + auto it = message_receivers.find(receiver.value); + eosio::check(it != message_receivers.end(), "receiver not registered"); + + intx::uint256 min_fee((uint64_t)it->min_fee.amount); + min_fee *= minimum_natively_representable; + + auto value = intx::be::unsafe::load(message.value.bytes); + eosio::check(value >= min_fee, "min_fee not covered"); + + emit_messages.push_back(emit); + }); + auto receipt = execute_tx(miner, block, tx, ep, true); + // Process emit messages + for(const auto& emit : emit_messages) { + balances balance_table(get_self(), get_self().value); + const balance& receiver_account = balance_table.get(emit.get_account_as_name().value, "receiver account is not open"); + + bridge_emit_message msg { + .receiver = emit.get_account_as_name(), + .sender = to_bytes(emit.message.sender), + .timestamp = emit.timestamp, + .value = to_bytes(emit.message.value), + .data = emit.data + }; + + action(std::vector{}, emit.get_account_as_name(), "onbridgemsg"_n, msg + ).send(); + + auto value = intx::be::unsafe::load(emit.message.value.bytes); + balance_table.modify(receiver_account, eosio::same_payer, [&](balance& a) { + a.balance += value; + }); + } + engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); LOGTIME("EVM END"); From bd531a3b8fab66655fd97d0b71b578550b01f2b7 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Mon, 24 Jul 2023 12:30:36 -0300 Subject: [PATCH 03/26] Add tests for 'emit' message functionality --- tests/CMakeLists.txt | 1 + tests/basic_evm_tester.cpp | 20 +- tests/basic_evm_tester.hpp | 23 +- tests/bridge_emit_tests.cpp | 239 ++++++++++++++++++ tests/contracts.hpp.in | 3 + .../contracts/evm_bridge_receiver/README.txt | 4 + .../evm_bridge_receiver.abi | 44 ++++ .../evm_bridge_receiver.contracts.md | 3 + .../evm_bridge_receiver.cpp | 30 +++ .../evm_bridge_receiver.hpp | 11 + .../evm_bridge_receiver.wasm | Bin 0 -> 3112 bytes tests/utils.hpp | 46 ++++ 12 files changed, 420 insertions(+), 4 deletions(-) create mode 100644 tests/bridge_emit_tests.cpp create mode 100644 tests/contracts/evm_bridge_receiver/README.txt create mode 100644 tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi create mode 100644 tests/contracts/evm_bridge_receiver/evm_bridge_receiver.contracts.md create mode 100644 tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp create mode 100644 tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp create mode 100755 tests/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm create mode 100644 tests/utils.hpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dcb168fd..715fa48c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -31,6 +31,7 @@ add_eosio_test_executable( unit_test ${CMAKE_SOURCE_DIR}/blockhash_tests.cpp ${CMAKE_SOURCE_DIR}/exec_tests.cpp ${CMAKE_SOURCE_DIR}/chainid_tests.cpp + ${CMAKE_SOURCE_DIR}/bridge_emit_tests.cpp ${CMAKE_SOURCE_DIR}/main.cpp ${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/encode.cpp ${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/decode.cpp diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index a9d7ece5..628137ff 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -139,6 +139,10 @@ evmc::address basic_evm_tester::make_reserved_address(uint64_t account) // clang-format on } +evmc::address basic_evm_tester::make_reserved_address(name account) { + return make_reserved_address(account.to_uint64_t()); +} + basic_evm_tester::basic_evm_tester(std::string native_symbol_str) : native_symbol(symbol::from_string(native_symbol_str)) { @@ -286,12 +290,24 @@ basic_evm_tester::generate_tx(const evmc::address& to, const intx::uint256& valu .value = value, }; } + +transaction_trace_ptr basic_evm_tester::bridgereg(name receiver, asset min_fee, vector extra_signers) { + extra_signers.push_back(receiver); + return basic_evm_tester::push_action(evm_account_name, "bridgereg"_n, extra_signers, + mvo()("receiver", receiver)("min_fee", min_fee)); +} + +transaction_trace_ptr basic_evm_tester::bridgeunreg(name receiver) { + return basic_evm_tester::push_action(evm_account_name, "bridgeunreg"_n, receiver, + mvo()("receiver", receiver)); +} + transaction_trace_ptr basic_evm_tester::exec(const exec_input& input, const std::optional& callback) { auto binary_data = fc::raw::pack>(input, callback); return basic_evm_tester::push_action(evm_account_name, "exec"_n, evm_account_name, bytes{binary_data.begin(), binary_data.end()}); } -void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) +transaction_trace_ptr basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) { silkworm::Bytes rlp; silkworm::rlp::encode(rlp, trx); @@ -300,7 +316,7 @@ void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) rlp_bytes.resize(rlp.size()); memcpy(rlp_bytes.data(), rlp.data(), rlp.size()); - push_action(evm_account_name, "pushtx"_n, miner, mvo()("miner", miner)("rlptx", rlp_bytes)); + return push_action(evm_account_name, "pushtx"_n, miner, mvo()("miner", miner)("rlptx", rlp_bytes)); } evmc::address basic_evm_tester::deploy_contract(evm_eoa& eoa, evmc::bytes bytecode) diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 56b79940..702f0a60 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -109,6 +109,19 @@ struct exec_output { std::optional context; }; +struct message_receiver { + name account; + asset min_fee; +}; + +struct bridge_emit_message { + name receiver; + bytes sender; + time_point timestamp; + bytes value; + bytes data; +}; + } // namespace evm_test @@ -123,6 +136,9 @@ FC_REFLECT(evm_test::exec_input, (context)(from)(to)(data)(value)) FC_REFLECT(evm_test::exec_callback, (contract)(action)) FC_REFLECT(evm_test::exec_output, (status)(data)(context)) +FC_REFLECT(evm_test::message_receiver, (account)(min_fee)); +FC_REFLECT(evm_test::bridge_emit_message, (receiver)(sender)(timestamp)(value)(data)); + namespace evm_test { class evm_eoa { @@ -166,6 +182,7 @@ class basic_evm_tester : public testing::validating_tester const symbol native_symbol; static evmc::address make_reserved_address(uint64_t account); + static evmc::address make_reserved_address(name account); explicit basic_evm_tester(std::string native_symbol_str = "4,EOS"); @@ -198,8 +215,10 @@ class basic_evm_tester : public testing::validating_tester silkworm::Transaction generate_tx(const evmc::address& to, const intx::uint256& value, uint64_t gas_limit = 21000) const; + transaction_trace_ptr bridgereg(name receiver, asset min_fee, vector extra_signers={evm_account_name}); + transaction_trace_ptr bridgeunreg(name receiver); transaction_trace_ptr exec(const exec_input& input, const std::optional& callback); - void pushtx(const silkworm::Transaction& trx, name miner = evm_account_name); + transaction_trace_ptr pushtx(const silkworm::Transaction& trx, name miner = evm_account_name); evmc::address deploy_contract(evm_eoa& eoa, evmc::bytes bytecode); void addegress(const std::vector& accounts); @@ -308,4 +327,4 @@ class speculative_block_starter bool canceled = false; }; -} // namespace evm_test \ No newline at end of file +} // namespace evm_test diff --git a/tests/bridge_emit_tests.cpp b/tests/bridge_emit_tests.cpp new file mode 100644 index 00000000..6a4f78c4 --- /dev/null +++ b/tests/bridge_emit_tests.cpp @@ -0,0 +1,239 @@ +#include "basic_evm_tester.hpp" +#include + +#include "utils.hpp" + +using intx::operator""_u256; + +using namespace evm_test; +using eosio::testing::eosio_assert_message_is; +using eosio::testing::expect_assert_message; + +struct bridge_emit_evm_tester : basic_evm_tester { + bridge_emit_evm_tester() { + create_accounts({"alice"_n}); + transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); + init(); + } + + std::string int_str32(uint32_t x) { + std::stringstream hex_ss; + hex_ss << std::hex << x; + int hex_length = hex_ss.str().length(); + + std::stringstream ss; + ss << std::setfill('0') << std::setw(64 - hex_length) << 0 << std::hex << std::uppercase << x; + return ss.str(); + } + + std::string str_to_hex(const std::string& str) { + std::stringstream ss; + for (char c : str) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(c); + } + return ss.str(); + } + + std::string data_str32(const std::string& str) { + std::stringstream ss; + ss << str; + int ps = 64 - (str.length() % 64); + if (ps == 64) {ps = 0;} + ss << std::setw(ps) << std::setfill('0') << ""; + return ss.str(); + } + + transaction_trace_ptr send_emit_message(evm_eoa& eoa, const std::string& receiver, const intx::uint256& value, const std::string& str_data) { + + silkworm::Bytes data; + data += evmc::from_hex("7e95a247").value(); + data += evmc::from_hex(int_str32(64)).value(); + data += evmc::from_hex(int_str32(128)).value(); + data += evmc::from_hex(int_str32(receiver.length())).value(); + data += evmc::from_hex(data_str32(str_to_hex(receiver))).value(); + data += evmc::from_hex(int_str32(str_data.size()/2)).value(); + data += evmc::from_hex(data_str32(str_data)).value(); + + return send_raw_message(eoa, make_reserved_address("evm"_n), value, data); + } + + transaction_trace_ptr send_raw_message(evm_eoa& eoa, const evmc::address& dest, const intx::uint256& value, const silkworm::Bytes& data) { + auto txn = generate_tx(dest, value, 250'000); + txn.data = data; + eoa.sign(txn); + return pushtx(txn); + } +}; + +BOOST_AUTO_TEST_SUITE(bridge_emit_tests) +BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_emit_evm_tester) try { + + create_accounts({"rec1"_n}); + + // evm auth is needed + BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, make_asset(-1), {}), + missing_auth_exception, [](const missing_auth_exception& e) { return expect_assert_message(e, "missing authority of evm"); }); + + // min fee only accepts EOS + BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, asset::from_string("1.0000 TST")), + eosio_assert_message_exception, eosio_assert_message_is("unexpected symbol")); + + // min fee must be non-negative + BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, make_asset(-1)), + eosio_assert_message_exception, eosio_assert_message_is("min_fee cannot be negative")); + + bridgereg("rec1"_n, make_asset(0)); + + auto row = fc::raw::unpack(get_row_by_account( "evm"_n, "evm"_n, "msgreceiver"_n, "rec1"_n)); + BOOST_REQUIRE(row.account == "rec1"_n); + BOOST_REQUIRE(row.min_fee == make_asset(0)); + + // Register again changing min fee + bridgereg("rec1"_n, make_asset(1)); + + row = fc::raw::unpack(get_row_by_account( "evm"_n, "evm"_n, "msgreceiver"_n, "rec1"_n)); + BOOST_REQUIRE(row.account == "rec1"_n); + BOOST_REQUIRE(row.min_fee == make_asset(1)); + + // Unregister rec1 + bridgeunreg("rec1"_n); + const auto& d = get_row_by_account( "evm"_n, "evm"_n, "msgreceiver"_n, "rec1"_n); + BOOST_REQUIRE(d.size() == 0); + produce_block(); + + // Try to unregister again + BOOST_REQUIRE_EXCEPTION(bridgeunreg("rec1"_n), + eosio_assert_message_exception, eosio_assert_message_is("receiver not found")); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(basic_tests, bridge_emit_evm_tester) try { + + // Create destination account + create_accounts({"rec1"_n}); + set_code("rec1"_n, testing::contracts::evm_bridge_receiver_wasm()); + set_abi("rec1"_n, testing::contracts::evm_bridge_receiver_abi().data()); + + // Register rec1 with 1.0000 EOS as min_fee + bridgereg("rec1"_n, make_asset(10000)); + + // Fund evm1 address with 100 EOS + evm_eoa evm1; + const int64_t to_bridge = 1000000; + transfer_token("alice"_n, "evm"_n, make_asset(to_bridge), evm1.address_0x()); + + // Emit message + auto res = send_emit_message(evm1, "rec1", 1_ether, "0102030405060708090a"); + + // Recover message form the return value of rec1 contract + BOOST_CHECK(res->action_traces[1].receiver == "rec1"_n); + auto out = fc::raw::unpack(res->action_traces[1].return_value); + + BOOST_CHECK(out.receiver == "rec1"_n); + BOOST_CHECK(out.sender == to_bytes(evm1.address)); + BOOST_CHECK(out.timestamp.time_since_epoch() == control->pending_block_time().time_since_epoch()); + BOOST_CHECK(out.value == to_bytes(1_ether)); + BOOST_CHECK(out.data == to_bytes(evmc::from_hex("0102030405060708090a").value())); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { + + auto emv_reserved_address = make_reserved_address("evm"_n); + + // Fund evm1 address with 100 EOS + evm_eoa evm1; + const int64_t to_bridge = 1000000; + transfer_token("alice"_n, "evm"_n, make_asset(to_bridge), evm1.address_0x()); + + // Send message without call data [ok] + send_raw_message(evm1, emv_reserved_address, 0, silkworm::Bytes{}); + + // Send message with 1 byte call data (we need at least 4 bytes for the function signature) + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("01").value()), + eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); + evm1.next_nonce--; + + // Send message with 4 bytes NOT matching the 'emit_(string,bytes)' signature + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("01020304").value()), + eosio_assert_message_exception, eosio_assert_message_is("unable to decode call message")); + evm1.next_nonce--; + + // Send message with 4 bytes matching the 'emit_(string,bytes)' signature + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("7e95a247").value()), + eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); + evm1.next_nonce--; + + // Wrong p1 offset + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("7e95a247").value() + + evmc::from_hex(int_str32(11)).value()), + eosio_assert_message_exception, eosio_assert_message_is("invalid p1 offset")); + evm1.next_nonce--; + + // Wrong p2 offset + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, + evmc::from_hex("7e95a247").value() + + evmc::from_hex(int_str32(64)).value() + + evmc::from_hex(int_str32(11)).value()), + eosio_assert_message_exception, eosio_assert_message_is("invalid p2 offset")); + evm1.next_nonce--; + + // abcd is not an account + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, + evmc::from_hex("7e95a247").value() + + evmc::from_hex(int_str32(64)).value() + + evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("data"))).value()), + eosio_assert_message_exception, eosio_assert_message_is("receiver is not account")); + evm1.next_nonce--; + + // Create destination account + create_accounts({"abcd"_n}); + + // abcd not registered + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, + evmc::from_hex("7e95a247").value() + + evmc::from_hex(int_str32(64)).value() + + evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("data"))).value()), + eosio_assert_message_exception, eosio_assert_message_is("receiver not registered")); + evm1.next_nonce--; + + // Register rec1 with 0 EOS as min_fee + bridgereg("abcd"_n, make_asset(1)); + + // min fee not covered + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, + evmc::from_hex("7e95a247").value() + + evmc::from_hex(int_str32(64)).value() + + evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("data"))).value()), + eosio_assert_message_exception, eosio_assert_message_is("min_fee not covered")); + evm1.next_nonce--; + + // Close abcd balance + close("abcd"_n); + + // receiver account is not open + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 1e14, + evmc::from_hex("7e95a247").value() + + evmc::from_hex(int_str32(64)).value() + + evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + + evmc::from_hex(int_str32(4)).value() + + evmc::from_hex(data_str32(str_to_hex("data"))).value()), + eosio_assert_message_exception, eosio_assert_message_is("receiver account is not open")); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/contracts.hpp.in b/tests/contracts.hpp.in index 7b308181..7610a3f9 100644 --- a/tests/contracts.hpp.in +++ b/tests/contracts.hpp.in @@ -40,6 +40,9 @@ struct contracts { static std::vector evm_read_callback_wasm() { return read_wasm("${CMAKE_CURRENT_SOURCE_DIR}/contracts/evm_read_callback/evm_read_callback.wasm"); } static std::vector evm_read_callback_abi() { return read_abi("${CMAKE_CURRENT_SOURCE_DIR}/contracts/evm_read_callback/evm_read_callback.abi"); } + static std::vector evm_bridge_receiver_wasm() { return read_wasm("${CMAKE_CURRENT_SOURCE_DIR}/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm"); } + static std::vector evm_bridge_receiver_abi() { return read_abi("${CMAKE_CURRENT_SOURCE_DIR}/contracts/evm_bridge_receiver/evm_bridge_receiver.abi"); } + static std::string eth_test_folder() { return "${CMAKE_CURRENT_SOURCE_DIR}/../silkworm/third_party/tests"; } diff --git a/tests/contracts/evm_bridge_receiver/README.txt b/tests/contracts/evm_bridge_receiver/README.txt new file mode 100644 index 00000000..5bd9b7bb --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/README.txt @@ -0,0 +1,4 @@ + --- evm_bridge_receiver Project --- + + - How to Build - + - run the command 'cdt-cpp -abigen -o evm_bridge_receiver.wasm evm_bridge_receiver.cpp' diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi new file mode 100644 index 00000000..95c4f868 --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi @@ -0,0 +1,44 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "onbridgemsg", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "sender", + "type": "bytes" + }, + { + "name": "timestamp", + "type": "time_point" + }, + { + "name": "value", + "type": "bytes" + }, + { + "name": "data", + "type": "bytes" + } + ] + } + ], + "actions": [ + { + "name": "onbridgemsg", + "type": "onbridgemsg", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} \ No newline at end of file diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.contracts.md b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.contracts.md new file mode 100644 index 00000000..5c7d6556 --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.contracts.md @@ -0,0 +1,3 @@ +

hi

+ +Stub for hi action's ricardian contract \ No newline at end of file diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp new file mode 100644 index 00000000..1c563064 --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp @@ -0,0 +1,30 @@ +#include "evm_bridge_receiver.hpp" + +extern "C" { +__attribute__((eosio_wasm_import)) +void set_action_return_value(void*, size_t); +} + +struct msgin { + name receiver; + bytes sender; + time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(msgin, (receiver)(sender)(timestamp)(value)(data)); +}; + +void evm_bridge_receiver::onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data) { + + msgin output { + .receiver = receiver, + .sender = sender, + .timestamp = timestamp, + .value = value, + .data = data + }; + + auto output_bin = eosio::pack(output); + set_action_return_value(output_bin.data(), output_bin.size()); +} diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp new file mode 100644 index 00000000..eb0f87c7 --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp @@ -0,0 +1,11 @@ +#include +#include + +using namespace eosio; + +typedef std::vector bytes; +CONTRACT evm_bridge_receiver : public contract { + public: + using contract::contract; + [[eosio::action]] void onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data); +}; \ No newline at end of file diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm new file mode 100755 index 0000000000000000000000000000000000000000..48733b0a9b5759ec7b1cd840cfdaef522d8ae810 GIT binary patch literal 3112 zcma)8ONbmr82;AWRk!oLUl0d>cG?n{9Vn-R|9;$xJ5_ z4?C;m;30zE;=!BXMJ3?D$7uzR9)b_hgAz~>1o7^{`2AJ;XfZ)|nXan8{>S$}yD6Kj z3nCJa-L*utB&SnaQq$>FOqU2hoOhm^IB(*-iTlk>JO$qfunQc*=_Z75khqec$&Si5 zE(O^O!*NB#`*uE@tPTg+WRim&v6dB>8Z3l2?Olxmq1=490nNalA3Ol&xLN zJ!`ykvyxTWV6ysZPKt%%ah|R0O7R_OCL7lE!rE}TTz)xQ-589rakidU`FMiua+$Aq zrRJsoVsqM#u(r{7I2um!72d2|T!tzN*i?J@;&_~IRD)`DJ*SXKhgmJ-y_DCuovP+& zyt+|M25TO`fqF~^%fpqN8tOny&6yU>v|3TCEp$g~9f*+-B2a=vctlNw-P)#TO7`t9 z_Ljzs2(r;=?K0if`s7foDLxQwlA-wSxMRqOXLWyZ>-zWKe*eplzrHs$5@#mI#{xr( zTl}~E`L*xpyzX0f{^6UCe}DZCirY~X)b4i_f&HUDfBJ1l5hPyQ+Nw!Jmf|DPSg5Il zl@3{&1<6uZUd7qQmr_xrvWJtWUg@XGP+?~aJGzikPhROmP};aM^f-%SFAC;20-Hoc zxE6h~(?|K%zO_xUh>VIPMLi*=_rgY*;-)9t5Qg1$SJJ-vE!Z{AjHO4gUhPK@3ONNd zVd!`wWM4slJeyKSSW2L%D?jL`@=QkGpSRUrZNZ?@bdqSx!Q|)my5)^X8Oh$Vg4Wo;+ z1zE=NRUL;s4eJs=1!Vyvzyk<0BhZXM!JY;J)X!-K)01a;fySPEqArvMdkAEr7=bjw zE&}FH6=WF!q7eI}aI9K|QH5!ircoMM+PFwGsLk@jM4&9-6BEQ@w7PczKWHVfdKGa(9>3#Tiai3Xj+{XDzhlh66(88F|V0?)Z3z6U7= zYpR`~(Phj*^dt_S6Kq5CA?_d0Py4Vs`q>(Np+?JmU;aiY~8hRA3K&Z7`8R3@5@T5*<3(+J;W3=#thu7o|=#Ns>w12?|lGzi2W z@gfe^HG#PUEgTi69%^mlF*nsd<*DX~9;dKP?w*|(HBs&yp*etL1=u!=_(b}DG^yKY zVg*Rx2`u+xiQZKh->kz?qhuI-ja5>XDbKCnoGAGHviM?K&i z2gf*pu4UbVj58fRoHdH4;R1Znw&A9|{n>f0P@wF&LOmhO&Ur||P40Z;u_sS@;Vw>? zW_Wgs`1mnTG|&hY(9ea=VZFU_Q&Y~Yb^UW*RlKJ^_WId&)88S%-nKK$BXq&xzyq2U z#s@0xsK-Knkh%z9#QQ#k&A3KCT%&hu^!pth3eoO}>Mf!0{k5s_(n2RCauE{~m8}(W zgvfFgASrk^zWK#Js^|ft1h0H9Tl5f@kTQY2MHdkLTjD}0s}HYmwrzu_T$rFQ?3TEI zDd&>*4ANO|W10Xj2AF(bqlzu14{)N2NKN4jRU-n--em0f+#CHs^_rT04)? z(Wt!BRP^Hs-el&@FAO3xfx|I;_#l^1?xyy_$;m8o9NO}?>0cVgvFY1pW7;yae$L!_@6qQ3#l(G?5; literal 0 HcmV?d00001 diff --git a/tests/utils.hpp b/tests/utils.hpp new file mode 100644 index 00000000..ce9f6a4f --- /dev/null +++ b/tests/utils.hpp @@ -0,0 +1,46 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace eosio; +using namespace std; +typedef intx::uint<256> u256; + +key256_t to_key256(const uint8_t* ptr, size_t len); +key256_t to_key256(const evmc::address& addr); +key256_t to_key256(const evmc::bytes32& data); +key256_t to_key256(const bytes& data); +bytes to_bytes(const u256& val); +bytes to_bytes(const silkworm::Bytes& b); +bytes to_bytes(const silkworm::ByteView& b); +bytes to_bytes(const evmc::bytes32& val); +bytes to_bytes(const evmc::address& addr); +bytes to_bytes(const key256_t& k); +evmc::address to_address(const bytes& addr); + \ No newline at end of file From 2850d3034d7497620bc9107cfb90f386bb9fbe93 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Mon, 24 Jul 2023 17:22:30 -0300 Subject: [PATCH 04/26] Add more tests for the 'emit' message functionality --- include/evm_runtime/bridge.hpp | 2 +- tests/bridge_emit_tests.cpp | 74 +++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp index 28489a74..41ceeab0 100644 --- a/include/evm_runtime/bridge.hpp +++ b/include/evm_runtime/bridge.hpp @@ -5,7 +5,7 @@ namespace evm_runtime { namespace bridge { struct emit { - static constexpr uint32_t id = 0x7e95a247; //sha3('emit_(string,bytes)')[:4] + static constexpr uint32_t id = 0x44282a35; //sha3('emit_(string,bytes)')[:4] string account; bytes data; diff --git a/tests/bridge_emit_tests.cpp b/tests/bridge_emit_tests.cpp index 6a4f78c4..9c8c4ea8 100644 --- a/tests/bridge_emit_tests.cpp +++ b/tests/bridge_emit_tests.cpp @@ -46,7 +46,7 @@ struct bridge_emit_evm_tester : basic_evm_tester { transaction_trace_ptr send_emit_message(evm_eoa& eoa, const std::string& receiver, const intx::uint256& value, const std::string& str_data) { silkworm::Bytes data; - data += evmc::from_hex("7e95a247").value(); + data += evmc::from_hex("44282a35").value(); data += evmc::from_hex(int_str32(64)).value(); data += evmc::from_hex(int_str32(128)).value(); data += evmc::from_hex(int_str32(receiver.length())).value(); @@ -160,19 +160,19 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { evm1.next_nonce--; // Send message with 4 bytes matching the 'emit_(string,bytes)' signature - BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("7e95a247").value()), + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("44282a35").value()), eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); evm1.next_nonce--; // Wrong p1 offset - BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("7e95a247").value() + + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("44282a35").value() + evmc::from_hex(int_str32(11)).value()), eosio_assert_message_exception, eosio_assert_message_is("invalid p1 offset")); evm1.next_nonce--; // Wrong p2 offset BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("7e95a247").value() + + evmc::from_hex("44282a35").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(11)).value()), eosio_assert_message_exception, eosio_assert_message_is("invalid p2 offset")); @@ -180,7 +180,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // abcd is not an account BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("7e95a247").value() + + evmc::from_hex("44282a35").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -195,7 +195,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // abcd not registered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("7e95a247").value() + + evmc::from_hex("44282a35").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -205,12 +205,12 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { eosio_assert_message_exception, eosio_assert_message_is("receiver not registered")); evm1.next_nonce--; - // Register rec1 with 0 EOS as min_fee + // Register abcd bridgereg("abcd"_n, make_asset(1)); // min fee not covered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("7e95a247").value() + + evmc::from_hex("44282a35").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -225,7 +225,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // receiver account is not open BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 1e14, - evmc::from_hex("7e95a247").value() + + evmc::from_hex("44282a35").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -236,4 +236,60 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE(emit_from_solidity, bridge_emit_evm_tester) try { + // // SPDX-License-Identifier: GPL-3.0 + // pragma solidity >=0.7.0 <0.9.0; + // contract Emiter { + // function go(string memory destination, uint256 n) public { + // address eosevm = 0xBbBBbbBBbBbbbBBBbBbbBBbB56E4000000000000; + // for(uint i=0; i start('rec1', 3) + auto txn = generate_tx(contract_addr, 0, 500'000); + txn.data = evmc::from_hex("a5cc93e4").value(); + txn.data += evmc::from_hex("0000000000000000000000000000000000000000000000000000000000000040").value(); + txn.data += evmc::from_hex("0000000000000000000000000000000000000000000000000000000000000003").value(); + txn.data += evmc::from_hex("0000000000000000000000000000000000000000000000000000000000000004").value(); + txn.data += evmc::from_hex("7265633100000000000000000000000000000000000000000000000000000000").value(); + evm1.sign(txn); + auto res = pushtx(txn); + BOOST_CHECK(res->action_traces.size() == 4); + + for(int i=0; i<3; ++i) { + auto out = fc::raw::unpack(res->action_traces[1+i].return_value); + BOOST_CHECK(out.receiver == "rec1"_n); + BOOST_CHECK(out.sender == to_bytes(contract_addr)); + BOOST_CHECK(out.timestamp.time_since_epoch() == control->pending_block_time().time_since_epoch()); + BOOST_CHECK(out.value == to_bytes(0_ether)); + BOOST_CHECK(out.data == to_bytes(evmc::from_hex("00000000000000000000000000000000000000000000000000000000FFFFFF0"+std::to_string(i)).value())); + } + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 1e300f2e959c1fd34928fec53bfb549df0f8b4ef Mon Sep 17 00:00:00 2001 From: yarkin Date: Thu, 27 Jul 2023 23:08:14 +0800 Subject: [PATCH 05/26] Add call actions. Pending submodule silkworm updates. --- include/evm_runtime/evm_contract.hpp | 6 + src/actions.cpp | 92 ++++++++-- tests/CMakeLists.txt | 1 + tests/basic_evm_tester.cpp | 28 +++ tests/basic_evm_tester.hpp | 2 + tests/call_tests.cpp | 252 +++++++++++++++++++++++++++ 6 files changed, 366 insertions(+), 15 deletions(-) create mode 100644 tests/call_tests.cpp diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 81b47620..668fd439 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -84,6 +84,10 @@ class [[eosio::contract]] evm_contract : public contract /// @return true if all garbage has been collected [[eosio::action]] bool gc(uint32_t max); + + [[eosio::action]] void call(eosio::name from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit); + [[eosio::action]] void admincall(const bytes& from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit); + #ifdef WITH_TEST_ACTIONS [[eosio::action]] void testtx(const std::optional& orlptx, const evm_runtime::test::block_info& bi); [[eosio::action]] void @@ -140,6 +144,8 @@ 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 evmc::address& to, intx::uint256 value, 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>; diff --git a/src/actions.cpp b/src/actions.cpp index 893aa4ee..ff8e0d48 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -40,6 +40,7 @@ namespace silkworm { namespace evm_runtime { static constexpr uint32_t hundred_percent = 100'000; +static constexpr char err_msg_invalid_addr[] = "invalid address"; using namespace silkworm; @@ -219,30 +220,37 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& inevm.emplace(get_self(), get_self().value); }; + bool is_special_signature = (tx.r == intx::uint256()); + tx.from.reset(); tx.recover_sender(); eosio::check(tx.from.has_value(), "unable to recover sender"); LOGTIME("EVM RECOVER SENDER"); + // type 1: normal signature (normal address recovered from normal signature), required !from_self + // type 2: special signature (r == 0), reserved address (stored in s), required from_self + reduce special balance + // type 3: special signature (r == 0), normal address (stored in s), required from_self if(from_self) { - check(is_reserved_address(*tx.from), "actions from self without a reserved from address are unexpected"); - const name ingress_account(*extract_reserved_address(*tx.from)); + check(is_special_signature, "actions from self without a special signature are unexpected"); + if (is_reserved_address(*tx.from)) { + const name ingress_account(*extract_reserved_address(*tx.from)); - const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas; - check(max_gas_cost + tx.value < std::numeric_limits::max(), "too much gas"); - const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost; + const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas; + check(max_gas_cost + tx.value < std::numeric_limits::max(), "too much gas"); + const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost; - populate_bridge_accessors(); - balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){ - b.balance -= value_with_max_gas; - }); - inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer); + populate_bridge_accessors(); + balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){ + b.balance -= value_with_max_gas; + }); + inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer); - ep.state().set_balance(*tx.from, value_with_max_gas); - ep.state().set_nonce(*tx.from, tx.nonce); + ep.state().set_balance(*tx.from, value_with_max_gas); + ep.state().set_nonce(*tx.from, tx.nonce); + } } - else if(is_reserved_address(*tx.from)) - check(from_self, "bridge signature used outside of bridge transaction"); + else if(is_special_signature) + check(false, "bridge signature used outside of bridge transaction"); if(enforce_chain_id && !from_self) { check(tx.chain_id.has_value(), "tx without chain-id"); @@ -452,7 +460,7 @@ void evm_contract::close(eosio::name owner) { uint64_t evm_contract::get_and_increment_nonce(const name owner) { nextnonces nextnonce_table(get_self(), get_self().value); - const nextnonce& nonce = nextnonce_table.get(owner.value); + const nextnonce& nonce = nextnonce_table.get(owner.value, "caller account has not been opened"); uint64_t ret = nonce.next_nonce; nextnonce_table.modify(nonce, eosio::same_payer, [](nextnonce& n){ ++n.next_nonce; @@ -555,6 +563,60 @@ bool evm_contract::gc(uint32_t max) { return state.gc(max); } +void evm_contract::call_(intx::uint256 s, const evmc::address& to, intx::uint256 value, bytes& data, uint64_t gas_limit, uint64_t nonce) { + const auto& current_config = _config.get(); + + 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.gas_limit = gas_limit, + txn.to = to, + txn.value = value, + txn.data = Bytes{(const uint8_t*)data.data(), data.size()}, + txn.r = 0u, // r == 0 is pseudo signature that resolves to reserved address range + txn.s = s; + + Bytes rlp; + rlp::encode(rlp, txn); + 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, uint128_t value, bytes& data, uint64_t gas_limit) { + assert_unfrozen(); + require_auth(from); + + ByteView bv_to{(const uint8_t*)to.data(), to.size()}; + + call_(from.value, to_evmc_address(bv_to), intx::uint256(value), data, gas_limit, get_and_increment_nonce(from)); +} + +void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit) { + assert_unfrozen(); + require_auth(get_self()); + + // Prepare s + eosio::check(from.size() == kAddressLength, err_msg_invalid_addr); + intx::uint256 s = intx::be::unsafe::load((const uint8_t *)from.data()); + // load will put the data in higher bytes, shift them donw. + s >>= 256 - kAddressLength * 8; + // pad with '1's + s |= ((~intx::uint256(0)) << (kAddressLength * 8)); + + // Prepare to + ByteView bv_to{(const uint8_t*)to.data(), to.size()}; + + // Prepare nonce + evm_runtime::state state{get_self(), get_self(), true}; + auto account = state.read_account(to_address(from)); + check(!!account, err_msg_invalid_addr); + + call_(s, to_evmc_address(bv_to), intx::uint256(value), data, gas_limit, account->nonce); +} + #ifdef WITH_TEST_ACTIONS [[eosio::action]] void evm_contract::testtx( const std::optional& orlptx, const evm_runtime::test::block_info& bi ) { assert_unfrozen(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dcb168fd..d0c91558 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ add_eosio_test_executable( unit_test ${CMAKE_SOURCE_DIR}/gas_fee_tests.cpp ${CMAKE_SOURCE_DIR}/blockhash_tests.cpp ${CMAKE_SOURCE_DIR}/exec_tests.cpp + ${CMAKE_SOURCE_DIR}/call_tests.cpp ${CMAKE_SOURCE_DIR}/chainid_tests.cpp ${CMAKE_SOURCE_DIR}/main.cpp ${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/encode.cpp diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index a9d7ece5..2e337e33 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -291,6 +291,34 @@ 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, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor) +{ + bytes to_bytes; + to_bytes.resize(to.size()); + memcpy(to_bytes.data(), to.data(), to.size()); + bytes data_bytes; + data_bytes.resize(data.size()); + memcpy(data_bytes.data(), data.data(), data.size()); + + push_action(evm_account_name, "call"_n, actor, mvo()("from", from)("to", to_bytes)("value", value)("data", data_bytes)("gas_limit", gas_limit)); +} + +void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor) +{ + bytes to_bytes; + to_bytes.resize(to.size()); + memcpy(to_bytes.data(), to.data(), to.size()); + bytes data_bytes; + data_bytes.resize(data.size()); + memcpy(data_bytes.data(), data.data(), data.size()); + + bytes from_bytes; + from_bytes.resize(to.size()); + memcpy(from_bytes.data(), from.data(), from.size()); + + push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value)("data", data_bytes)("gas_limit", gas_limit)); +} + void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) { silkworm::Bytes rlp; diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 56b79940..d2c3fa16 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -200,6 +200,8 @@ class basic_evm_tester : public testing::validating_tester transaction_trace_ptr exec(const exec_input& input, const std::optional& callback); void pushtx(const silkworm::Transaction& trx, name miner = evm_account_name); + void call(name from, const evmc::bytes& to, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor); + void admincall(const evmc::bytes& from, const evmc::bytes& to, uint128_t 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/call_tests.cpp b/tests/call_tests.cpp new file mode 100644 index 00000000..e93e7ca2 --- /dev/null +++ b/tests/call_tests.cpp @@ -0,0 +1,252 @@ +#include "basic_evm_tester.hpp" +#include + +using intx::operator""_u256; + +using namespace evm_test; +using eosio::testing::eosio_assert_message_is; +struct exec_output_row { + uint64_t id; + exec_output output; +}; +FC_REFLECT(exec_output_row, (id)(output)) + +struct call_evm_tester : basic_evm_tester { + call_evm_tester() { + create_accounts({"alice"_n}); + transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); + create_accounts({"bob"_n}); + transfer_token(faucet_account_name, "bob"_n, make_asset(10000'0000)); + init(); + } + + evmc::address deploy_test_contract(evm_eoa& eoa) { + + /* + //SPDX-License-Identifier: lgplv3 + pragma solidity ^0.8.0; + + contract Test { + uint256 public count; + address public lastcaller; + + function test(uint256 input) public { + count += input; + lastcaller = msg.sender; + } + } + */ + const std::string token_bytecode = + "608060405234801561001057600080fd5b506102ad806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061013f565b60405180910390f35b61007e6004803603810190610079919061018b565b6100a4565b005b610088610100565b60405161009591906101f9565b60405180910390f35b60005481565b806000808282546100b59190610243565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61013981610126565b82525050565b60006020820190506101546000830184610130565b92915050565b600080fd5b61016881610126565b811461017357600080fd5b50565b6000813590506101858161015f565b92915050565b6000602082840312156101a1576101a061015a565b5b60006101af84828501610176565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101e3826101b8565b9050919050565b6101f3816101d8565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e82610126565b915061025983610126565b925082820190508082111561027157610270610214565b5b9291505056fea2646970667358221220f1474068cf9aac836f8c5a31a9307e9bf225c1fccdedaf90eeaba493de1a9c9564736f6c63430008120033"; + // Deploy token contract + return deploy_contract(eoa, evmc::from_hex(token_bytecode).value()); + } + + void call_test(const evmc::address& contract_addr, uint64_t amount, name eos, name actor) { + + auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("29e99f07").value(); // sha3(test(uint256))[:4] + data += evmc::bytes32{amount}; // value + + call(eos, to, 0, data, 500000, actor); + } + + void admincall_test(const evmc::address& contract_addr, uint64_t amount, evm_eoa& eoa, name actor) { + auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + auto from = evmc::bytes{std::begin(eoa.address.bytes), std::end(eoa.address.bytes)}; + silkworm::Bytes data; + data += evmc::from_hex("29e99f07").value(); // sha3(test(uint256))[:4] + data += evmc::bytes32{amount}; // value + + admincall(from, to, 0, data, 500000, actor); + } + + intx::uint256 get_count(const evmc::address& contract_addr, std::optional callback={}, std::optional context={}) { + exec_input input; + input.context = context; + input.to = bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("06661abd").value(); // sha3(count())[:4] + input.data = bytes{data.begin(), data.end()}; + + auto res = exec(input, callback); + + BOOST_REQUIRE(res); + BOOST_REQUIRE(res->action_traces.size() == 1); + + // Since callback information was not provided the result of the + // execution is returned in the action return_value + auto out = fc::raw::unpack(res->action_traces[0].return_value); + BOOST_REQUIRE(out.status == 0); + BOOST_REQUIRE(out.data.size() == 32); + + auto result = intx::be::unsafe::load(reinterpret_cast(out.data.data())); + return result; + } + + evmc::address get_lastcaller(const evmc::address& contract_addr, std::optional callback={}, std::optional context={}) { + exec_input input; + input.context = context; + input.to = bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("d097e7a6").value(); // sha3(lastcaller())[:4] + input.data = bytes{data.begin(), data.end()}; + + auto res = exec(input, callback); + + BOOST_REQUIRE(res); + BOOST_REQUIRE(res->action_traces.size() == 1); + + // Since callback information was not provided the result of the + // execution is returned in the action return_value + auto out = fc::raw::unpack(res->action_traces[0].return_value); + BOOST_REQUIRE(out.status == 0); + + BOOST_REQUIRE(out.data.size() >= silkworm::kAddressLength); + + evmc::address result; + memcpy(result.bytes, out.data.data()+out.data.size() - silkworm::kAddressLength, silkworm::kAddressLength); + return result; + } + +}; + +BOOST_AUTO_TEST_SUITE(call_evm_tests) +BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { + evm_eoa evm1; + auto total_fund = intx::uint256(vault_balance("evm"_n)); + // Fund evm1 address with 100 EOS + transfer_token("alice"_n, "evm"_n, make_asset(1000000), evm1.address_0x()); + auto evm1_balance = evm_balance(evm1); + BOOST_REQUIRE(!!evm1_balance); + BOOST_REQUIRE(*evm1_balance == intx::exp<256>(10, 18) * 100); + total_fund += *evm1_balance; + + // Deploy contract + auto token_addr = deploy_test_contract(evm1); + + // Deployment gas fee go to evm vault + BOOST_REQUIRE(*evm_balance(evm1) + intx::uint256(vault_balance("evm"_n)) == total_fund); + + // Missing authority + BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 1234, "alice"_n, "bob"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + // Account not opened + BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 1234, "alice"_n, "alice"_n), + eosio_assert_message_exception, eosio_assert_message_is("caller account has not been opened")); + + // Open + open("alice"_n); + + // No sufficient funds in the account so decrementing of balance failed. + BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 1234, "alice"_n, "alice"_n), + eosio_assert_message_exception, eosio_assert_message_is("decrementing more than available")); + + // Transfer enough funds + transfer_token("alice"_n, "evm"_n, make_asset(1000000), "alice"); + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == intx::exp<256>(10, 18) * 100); + auto total_fund2 = intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n)); + + // Call and check results + call_test(token_addr, 1234, "alice"_n, "alice"_n); + auto count = get_count(token_addr); + BOOST_REQUIRE(count == 1234); + + // Gas go from alice's vault to evm's vault + BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n))); + + + // Advance block so we do not generate same transaction. + produce_block(); + + call_test(token_addr, 4321, "alice"_n, "alice"_n); + count = get_count(token_addr); + BOOST_REQUIRE(count == 5555); + + // Gas go from alice's vault to evm's vault + BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n))); + + // Function being called on behalf of reserved address of eos account "alice" + auto caller = get_lastcaller(token_addr); + BOOST_REQUIRE(caller == make_reserved_address("alice"_n.to_uint64_t())); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { + evm_eoa evm1; + evm_eoa evm2; + + auto total_fund = intx::uint256(vault_balance("evm"_n)); + + // Fund evm1 address with 100 EOS + transfer_token("alice"_n, "evm"_n, make_asset(1000000), evm1.address_0x()); + + auto evm1_balance = evm_balance(evm1); + BOOST_REQUIRE(!!evm1_balance); + BOOST_REQUIRE(*evm1_balance == intx::exp<256>(10, 18) * 100); + total_fund += *evm1_balance; + + // Deploy contract + auto token_addr = deploy_test_contract(evm1); + + // Deployment gas fee go to evm vault + BOOST_REQUIRE(*evm_balance(evm1) + intx::uint256(vault_balance("evm"_n)) == total_fund); + + // Missing authority + BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 1234, evm2, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + // Account not created + BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, "evm"_n), + eosio_assert_message_exception, eosio_assert_message_is("invalid address")); + + // Transfer small amount to create account + transfer_token("alice"_n, "evm"_n, make_asset(100), evm2.address_0x()); + + auto evm2_balance = evm_balance(evm2); + BOOST_REQUIRE(!!evm2_balance); + BOOST_REQUIRE(*evm2_balance == intx::exp<256>(10, 18 - 4) * 100); + + // Insufficient funds + BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, "evm"_n), + eosio_assert_message_exception, eosio_assert_message_is("validate_transaction error: 23 Insufficient funds")); + + // Transfer enough funds + transfer_token("alice"_n, "evm"_n, make_asset(10000), evm2.address_0x()); + + BOOST_REQUIRE(evm_balance(evm2) == intx::exp<256>(10, 18 - 4) * 10100); + auto total_fund2 = intx::uint256(vault_balance("evm"_n)) + *evm_balance(evm2); + + // Call and check results + admincall_test(token_addr, 1234, evm2, "evm"_n); + + auto count = get_count(token_addr); + BOOST_REQUIRE(count == 1234); + + // Gas go from evm2 to evm vault + BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance("evm"_n))); + + // Advance block so we do not generate same transaction. + produce_block(); + + admincall_test(token_addr, 4321, evm2, "evm"_n); + count = get_count(token_addr); + BOOST_REQUIRE(count == 5555); + + // Gas go from evm2 to evm vault + BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance("evm"_n))); + + // Function being called on behalf of evm address "evm2" + auto caller = get_lastcaller(token_addr); + BOOST_REQUIRE(caller== evm2.address); + + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 02f90c7f590074cff51f6edb3e0bff8110b01f2c Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Thu, 27 Jul 2023 20:51:47 -0300 Subject: [PATCH 06/26] Use EVM message filter functionality to process bridge messages --- include/evm_runtime/bridge.hpp | 2 -- silkworm | 2 +- src/actions.cpp | 55 ++++++++++++++++------------------ 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp index 41ceeab0..ea3a0ae3 100644 --- a/include/evm_runtime/bridge.hpp +++ b/include/evm_runtime/bridge.hpp @@ -18,8 +18,6 @@ struct emit { return *account_; } - eosio::time_point timestamp; - evmc_message message; mutable std::optional account_; }; diff --git a/silkworm b/silkworm index df5d9ca3..350cfc3c 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit df5d9ca396b4a85ace6b85aa16bbb0e5e0f90e50 +Subproject commit 350cfc3c4c71f0882bceb0290a166480cdc52e2d diff --git a/src/actions.cpp b/src/actions.cpp index e1b9eddb..df930642 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -407,20 +407,21 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { 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"); - // handler for all EVM call messages - std::vector emit_messages; - ep.set_evm_call_hook([&](const evmc_message& message, const evmc::Result& result){ - static auto me = make_reserved_address(get_self().value); - if (message.recipient != me || message.input_size == 0) return; + // Filter EVM messages (with data) that are sent to the reserved address + // corresponding to the EOS account holding the contract (self) + ep.set_evm_message_filter([&](const evmc_message& message) -> bool { + auto me = make_reserved_address(get_self().value); + return message.recipient == me && message.input_size > 0; + }); + + auto receipt = execute_tx(miner, block, tx, ep, true); - eosio::check(result.status_code == EVMC_SUCCESS, "error calling reserved address"); + for(const auto& msg : ep.state().filtered_messages()) { - auto call = bridge::decode_call_message(ByteView{message.input_data, message.input_size}); + auto call = bridge::decode_call_message(ByteView{msg.data}); eosio::check(call.has_value(), "unable to decode call message"); auto& emit = std::get(call.value()); - emit.message = message; - emit.timestamp = eosio::current_time_point(); const auto& receiver = emit.get_account_as_name(); eosio::check(eosio::is_account(receiver), "receiver is not account"); @@ -432,33 +433,29 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { intx::uint256 min_fee((uint64_t)it->min_fee.amount); min_fee *= minimum_natively_representable; - auto value = intx::be::unsafe::load(message.value.bytes); + auto value = intx::be::unsafe::load(msg.value.bytes); eosio::check(value >= min_fee, "min_fee not covered"); - emit_messages.push_back(emit); - }); - - auto receipt = execute_tx(miner, block, tx, ep, true); - - // Process emit messages - for(const auto& emit : emit_messages) { balances balance_table(get_self(), get_self().value); const balance& receiver_account = balance_table.get(emit.get_account_as_name().value, "receiver account is not open"); - bridge_emit_message msg { - .receiver = emit.get_account_as_name(), - .sender = to_bytes(emit.message.sender), - .timestamp = emit.timestamp, - .value = to_bytes(emit.message.value), - .data = emit.data - }; - - action(std::vector{}, emit.get_account_as_name(), "onbridgemsg"_n, msg + action(std::vector{}, emit.get_account_as_name(), "onbridgemsg"_n, + bridge_emit_message{ + .receiver = emit.get_account_as_name(), + .sender = to_bytes(msg.sender), + .timestamp = eosio::current_time_point(), + .value = to_bytes(msg.value), + .data = emit.data + } ).send(); - auto value = intx::be::unsafe::load(emit.message.value.bytes); - balance_table.modify(receiver_account, eosio::same_payer, [&](balance& a) { - a.balance += value; + balance_table.modify(receiver_account, eosio::same_payer, [&](balance& row) { + row.balance += value; + }); + + const balance& self_account = balance_table.get(get_self().value, "self account is not open"); + balance_table.modify(self_account, eosio::same_payer, [&](balance& row) { + row.balance -= value; }); } From 6e8750e3563a730bca00881d7a42e14255e05d01 Mon Sep 17 00:00:00 2001 From: yarkin Date: Fri, 28 Jul 2023 22:22:28 +0800 Subject: [PATCH 07/26] Allow call to create contracts. Allow admin call to call as reserved address. --- include/evm_runtime/evm_contract.hpp | 2 +- src/actions.cpp | 52 ++++++++++++++++------------ tests/basic_evm_tester.cpp | 4 ++- tests/call_tests.cpp | 42 ++++++++++++++++++++-- 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 668fd439..c3a96583 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -144,7 +144,7 @@ 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 evmc::address& to, intx::uint256 value, bytes& data, uint64_t gas_limit, uint64_t nonce); + void call_(intx::uint256 s, const bytes& to, intx::uint256 value, 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); diff --git a/src/actions.cpp b/src/actions.cpp index ff8e0d48..5ce4240d 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -563,22 +563,26 @@ bool evm_contract::gc(uint32_t max) { return state.gc(max); } -void evm_contract::call_(intx::uint256 s, const evmc::address& to, intx::uint256 value, bytes& data, uint64_t gas_limit, uint64_t nonce) { +void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, bytes& data, uint64_t gas_limit, uint64_t nonce) { const auto& current_config = _config.get(); 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.gas_limit = gas_limit, - txn.to = to, - txn.value = value, - txn.data = Bytes{(const uint8_t*)data.data(), data.size()}, - txn.r = 0u, // r == 0 is pseudo signature that resolves to reserved address range + 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.gas_limit = gas_limit; + txn.value = value; + txn.data = Bytes{(const uint8_t*)data.data(), data.size()}; + txn.r = 0u; // r == 0 is pseudo signature that resolves to reserved address range txn.s = s; + // Allow empty to so that it can support contract creation calls. + if (!to.empty()) { + ByteView bv_to{(const uint8_t*)to.data(), to.size()}; + txn.to = to_evmc_address(bv_to); + } + Bytes rlp; rlp::encode(rlp, txn); pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}}); @@ -589,9 +593,7 @@ void evm_contract::call(eosio::name from, const bytes& to, uint128_t value, byte assert_unfrozen(); require_auth(from); - ByteView bv_to{(const uint8_t*)to.data(), to.size()}; - - call_(from.value, to_evmc_address(bv_to), intx::uint256(value), data, gas_limit, get_and_increment_nonce(from)); + call_(from.value, to, intx::uint256(value), data, gas_limit, get_and_increment_nonce(from)); } void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit) { @@ -606,15 +608,21 @@ void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value // pad with '1's s |= ((~intx::uint256(0)) << (kAddressLength * 8)); - // Prepare to - ByteView bv_to{(const uint8_t*)to.data(), to.size()}; - // Prepare nonce - evm_runtime::state state{get_self(), get_self(), true}; - auto account = state.read_account(to_address(from)); - check(!!account, err_msg_invalid_addr); - - call_(s, to_evmc_address(bv_to), intx::uint256(value), data, gas_limit, account->nonce); + auto from_addr = to_address(from); + auto eos_acct = extract_reserved_address(from_addr); + uint64_t nonce = 0; + if (eos_acct) { + nonce = get_and_increment_nonce(eosio::name(*eos_acct)); + } + else { + evm_runtime::state state{get_self(), get_self(), true}; + auto account = state.read_account(from_addr); + check(!!account, err_msg_invalid_addr); + nonce = account->nonce; + } + + call_(s, to, intx::uint256(value), data, gas_limit, nonce); } #ifdef WITH_TEST_ACTIONS diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index 2e337e33..649486c3 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -296,6 +296,7 @@ void basic_evm_tester::call(name from, const evmc::bytes& to, uint128_t value, e bytes to_bytes; to_bytes.resize(to.size()); memcpy(to_bytes.data(), to.data(), to.size()); + bytes data_bytes; data_bytes.resize(data.size()); memcpy(data_bytes.data(), data.data(), data.size()); @@ -308,12 +309,13 @@ void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, bytes to_bytes; to_bytes.resize(to.size()); memcpy(to_bytes.data(), to.data(), to.size()); + bytes data_bytes; data_bytes.resize(data.size()); memcpy(data_bytes.data(), data.data(), data.size()); bytes from_bytes; - from_bytes.resize(to.size()); + from_bytes.resize(from.size()); memcpy(from_bytes.data(), from.data(), from.size()); push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value)("data", data_bytes)("gas_limit", gas_limit)); diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index e93e7ca2..45dc9c2f 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -12,6 +12,9 @@ struct exec_output_row { FC_REFLECT(exec_output_row, (id)(output)) struct call_evm_tester : basic_evm_tester { + const std::string contract_bytecode = + "608060405234801561001057600080fd5b506102ad806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061013f565b60405180910390f35b61007e6004803603810190610079919061018b565b6100a4565b005b610088610100565b60405161009591906101f9565b60405180910390f35b60005481565b806000808282546100b59190610243565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61013981610126565b82525050565b60006020820190506101546000830184610130565b92915050565b600080fd5b61016881610126565b811461017357600080fd5b50565b6000813590506101858161015f565b92915050565b6000602082840312156101a1576101a061015a565b5b60006101af84828501610176565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101e3826101b8565b9050919050565b6101f3816101d8565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e82610126565b915061025983610126565b925082820190508082111561027157610270610214565b5b9291505056fea2646970667358221220f1474068cf9aac836f8c5a31a9307e9bf225c1fccdedaf90eeaba493de1a9c9564736f6c63430008120033"; + call_evm_tester() { create_accounts({"alice"_n}); transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); @@ -36,10 +39,10 @@ struct call_evm_tester : basic_evm_tester { } } */ - const std::string token_bytecode = + const std::string contract_bytecode = "608060405234801561001057600080fd5b506102ad806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061013f565b60405180910390f35b61007e6004803603810190610079919061018b565b6100a4565b005b610088610100565b60405161009591906101f9565b60405180910390f35b60005481565b806000808282546100b59190610243565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61013981610126565b82525050565b60006020820190506101546000830184610130565b92915050565b600080fd5b61016881610126565b811461017357600080fd5b50565b6000813590506101858161015f565b92915050565b6000602082840312156101a1576101a061015a565b5b60006101af84828501610176565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101e3826101b8565b9050919050565b6101f3816101d8565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e82610126565b915061025983610126565b925082820190508082111561027157610270610214565b5b9291505056fea2646970667358221220f1474068cf9aac836f8c5a31a9307e9bf225c1fccdedaf90eeaba493de1a9c9564736f6c63430008120033"; // Deploy token contract - return deploy_contract(eoa, evmc::from_hex(token_bytecode).value()); + return deploy_contract(eoa, evmc::from_hex(contract_bytecode).value()); } void call_test(const evmc::address& contract_addr, uint64_t amount, name eos, name actor) { @@ -112,7 +115,6 @@ struct call_evm_tester : basic_evm_tester { memcpy(result.bytes, out.data.data()+out.data.size() - silkworm::kAddressLength, silkworm::kAddressLength); return result; } - }; BOOST_AUTO_TEST_SUITE(call_evm_tests) @@ -249,4 +251,38 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { } FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(deploy_contract_function, call_evm_tester) try { + auto alice_addr = make_reserved_address("alice"_n.to_uint64_t()); + open("alice"_n); + transfer_token("alice"_n, "evm"_n, make_asset(1000000), "alice"); + + + auto to = evmc::bytes(); + + auto data = evmc::from_hex(contract_bytecode); + + call("alice"_n, to, 0, *data, 1000000, "alice"_n); // nonce 0->1 + + auto addr = silkworm::create_address(alice_addr, 0); + + call_test(addr, 1234, "alice"_n, "alice"_n); // nonce 1->2 + auto count = get_count(addr); + BOOST_REQUIRE(count == 1234); + + // Advance block so we do not generate same transaction. + produce_block(); + + auto from = evmc::bytes{std::begin(alice_addr.bytes), std::end(alice_addr.bytes)}; + + admincall(from, to, 0, *data, 1000000, "evm"_n); // nonce 2->3 + + addr = silkworm::create_address(alice_addr, 2); + call_test(addr, 2222, "alice"_n, "alice"_n); // nonce 3->4 + count = get_count(addr); + BOOST_REQUIRE(count == 2222); +} FC_LOG_AND_RETHROW() + + + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From a6bd60689b908b693c10e0647689b64c80a60623 Mon Sep 17 00:00:00 2001 From: yarkin Date: Fri, 4 Aug 2023 13:41:09 +0800 Subject: [PATCH 08/26] Change error message when inline pushtx actions fails and add tests for reverted call/admincall action. --- src/actions.cpp | 2 +- tests/call_tests.cpp | 39 +++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 5ce4240d..d939034b 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -277,7 +277,7 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& } if(from_self) - eosio::check(receipt.success, "ingress bridge actions must succeed"); + eosio::check(receipt.success, "inline actions must succeed"); if(!ep.state().reserved_objects().empty()) { bool non_open_account_sent = false; diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index 45dc9c2f..f1fd36e2 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -12,20 +12,7 @@ struct exec_output_row { FC_REFLECT(exec_output_row, (id)(output)) struct call_evm_tester : basic_evm_tester { - const std::string contract_bytecode = - "608060405234801561001057600080fd5b506102ad806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061013f565b60405180910390f35b61007e6004803603810190610079919061018b565b6100a4565b005b610088610100565b60405161009591906101f9565b60405180910390f35b60005481565b806000808282546100b59190610243565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61013981610126565b82525050565b60006020820190506101546000830184610130565b92915050565b600080fd5b61016881610126565b811461017357600080fd5b50565b6000813590506101858161015f565b92915050565b6000602082840312156101a1576101a061015a565b5b60006101af84828501610176565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101e3826101b8565b9050919050565b6101f3816101d8565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e82610126565b915061025983610126565b925082820190508082111561027157610270610214565b5b9291505056fea2646970667358221220f1474068cf9aac836f8c5a31a9307e9bf225c1fccdedaf90eeaba493de1a9c9564736f6c63430008120033"; - - call_evm_tester() { - create_accounts({"alice"_n}); - transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); - create_accounts({"bob"_n}); - transfer_token(faucet_account_name, "bob"_n, make_asset(10000'0000)); - init(); - } - - evmc::address deploy_test_contract(evm_eoa& eoa) { - - /* + /* //SPDX-License-Identifier: lgplv3 pragma solidity ^0.8.0; @@ -34,13 +21,25 @@ struct call_evm_tester : basic_evm_tester { address public lastcaller; function test(uint256 input) public { + require(input != 0); + count += input; lastcaller = msg.sender; } } */ - const std::string contract_bytecode = - "608060405234801561001057600080fd5b506102ad806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061013f565b60405180910390f35b61007e6004803603810190610079919061018b565b6100a4565b005b610088610100565b60405161009591906101f9565b60405180910390f35b60005481565b806000808282546100b59190610243565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61013981610126565b82525050565b60006020820190506101546000830184610130565b92915050565b600080fd5b61016881610126565b811461017357600080fd5b50565b6000813590506101858161015f565b92915050565b6000602082840312156101a1576101a061015a565b5b60006101af84828501610176565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101e3826101b8565b9050919050565b6101f3816101d8565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e82610126565b915061025983610126565b925082820190508082111561027157610270610214565b5b9291505056fea2646970667358221220f1474068cf9aac836f8c5a31a9307e9bf225c1fccdedaf90eeaba493de1a9c9564736f6c63430008120033"; + const std::string contract_bytecode = + "608060405234801561001057600080fd5b506102ba806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061014c565b60405180910390f35b61007e60048036038101906100799190610198565b6100a4565b005b61008861010d565b6040516100959190610206565b60405180910390f35b60005481565b600081036100b157600080fd5b806000808282546100c29190610250565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61014681610133565b82525050565b6000602082019050610161600083018461013d565b92915050565b600080fd5b61017581610133565b811461018057600080fd5b50565b6000813590506101928161016c565b92915050565b6000602082840312156101ae576101ad610167565b5b60006101bc84828501610183565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101f0826101c5565b9050919050565b610200816101e5565b82525050565b600060208201905061021b60008301846101f7565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061025b82610133565b915061026683610133565b925082820190508082111561027e5761027d610221565b5b9291505056fea2646970667358221220edc009a00fe897643f99e8327c7fb355c96d7b91fb8a7da507513c6b2341acc564736f6c63430008120033"; + + call_evm_tester() { + create_accounts({"alice"_n}); + transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); + create_accounts({"bob"_n}); + transfer_token(faucet_account_name, "bob"_n, make_asset(10000'0000)); + init(); + } + + evmc::address deploy_test_contract(evm_eoa& eoa) { // Deploy token contract return deploy_contract(eoa, evmc::from_hex(contract_bytecode).value()); } @@ -155,6 +154,10 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == intx::exp<256>(10, 18) * 100); auto total_fund2 = intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n)); + BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 0, "alice"_n, "alice"_n), + eosio_assert_message_exception, eosio_assert_message_is("inline actions must succeed")); + + // Call and check results call_test(token_addr, 1234, "alice"_n, "alice"_n); auto count = get_count(token_addr); @@ -225,6 +228,10 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { BOOST_REQUIRE(evm_balance(evm2) == intx::exp<256>(10, 18 - 4) * 10100); auto total_fund2 = intx::uint256(vault_balance("evm"_n)) + *evm_balance(evm2); + + BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 0, evm2, "evm"_n), + eosio_assert_message_exception, eosio_assert_message_is("inline actions must succeed")); + // Call and check results admincall_test(token_addr, 1234, evm2, "evm"_n); From 48513d540df7a8510c4bae8bce7ebfd361b24553 Mon Sep 17 00:00:00 2001 From: yarkin Date: Fri, 4 Aug 2023 13:43:29 +0800 Subject: [PATCH 09/26] update submodule silkworm --- silkworm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silkworm b/silkworm index 2f000a5c..55a46c32 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 2f000a5c5ae74da14e97333ef37bae8b059c63ae +Subproject commit 55a46c321b9cf321729248831956be65067e2fd2 From 867fa2cc19052f641c17ac42e3d11e71b563bea7 Mon Sep 17 00:00:00 2001 From: yarkin Date: Mon, 7 Aug 2023 15:02:38 +0800 Subject: [PATCH 10/26] Add missing const. --- include/evm_runtime/evm_contract.hpp | 6 +++--- src/actions.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index c3a96583..cd737d10 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -85,8 +85,8 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] bool gc(uint32_t max); - [[eosio::action]] void call(eosio::name from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit); - [[eosio::action]] void admincall(const bytes& from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit); + [[eosio::action]] void call(eosio::name from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit); + [[eosio::action]] void admincall(const bytes& from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit); #ifdef WITH_TEST_ACTIONS [[eosio::action]] void testtx(const std::optional& orlptx, const evm_runtime::test::block_info& bi); @@ -144,7 +144,7 @@ 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, bytes& data, uint64_t gas_limit, uint64_t nonce); + 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); diff --git a/src/actions.cpp b/src/actions.cpp index d939034b..898d0602 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -563,7 +563,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, bytes& data, uint64_t gas_limit, uint64_t nonce) { +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(); Transaction txn; @@ -589,14 +589,14 @@ void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, pushtx_act.send(get_self(), rlp); } -void evm_contract::call(eosio::name from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit) { +void evm_contract::call(eosio::name from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit) { assert_unfrozen(); require_auth(from); call_(from.value, to, intx::uint256(value), data, gas_limit, get_and_increment_nonce(from)); } -void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value, bytes& data, uint64_t gas_limit) { +void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit) { assert_unfrozen(); require_auth(get_self()); From 7ae7a54b38c75484e632ad1baa4b91bf8727e26f Mon Sep 17 00:00:00 2001 From: Peter Oschwald Date: Tue, 8 Aug 2023 14:02:42 -0500 Subject: [PATCH 11/26] Optimize build artifact size by removing object files. --- .github/workflows/build-contract.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-contract.sh b/.github/workflows/build-contract.sh index 6b777230..2d336a31 100755 --- a/.github/workflows/build-contract.sh +++ b/.github/workflows/build-contract.sh @@ -20,6 +20,6 @@ ee make -j "$(nproc)" # pack ee popd -ee 'tar -czf contract.tar.gz build/*' +ee 'tar -czf contract.tar.gz --exclude="*.obj" build/*' echo "Done! - ${0##*/}" From 87dff735c56b4fb2bf54453e1dcb4459783d4b88 Mon Sep 17 00:00:00 2001 From: Peter Oschwald Date: Tue, 8 Aug 2023 14:06:51 -0500 Subject: [PATCH 12/26] Optimize tests build artifact size by removing object files. --- .github/workflows/build-contract-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-contract-test.sh b/.github/workflows/build-contract-test.sh index 0f868252..676e6274 100755 --- a/.github/workflows/build-contract-test.sh +++ b/.github/workflows/build-contract-test.sh @@ -23,7 +23,7 @@ ee make -j "$(nproc)" unit_test # pack ee popd -ee 'tar -czf ../contract-test.tar.gz build/*' +ee 'tar -czf ../contract-test.tar.gz --exclude="*.o" build/*' ee popd echo "Done! - ${0##*/}" From bc173c30ea824a7dc13f07a288c84f58d562dbcb Mon Sep 17 00:00:00 2001 From: yarkin Date: Wed, 9 Aug 2023 16:48:37 +0800 Subject: [PATCH 13/26] Optimze logic when processing tx with special signature. --- src/actions.cpp | 49 ++++++++++++++++++++++---------------------- tests/call_tests.cpp | 4 ++-- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 898d0602..91b792c2 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -220,39 +220,38 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& inevm.emplace(get_self(), get_self().value); }; - bool is_special_signature = (tx.r == intx::uint256()); + bool is_special_signature = silkworm::is_special_signature(tx.r, tx.s); tx.from.reset(); tx.recover_sender(); eosio::check(tx.from.has_value(), "unable to recover sender"); LOGTIME("EVM RECOVER SENDER"); - // type 1: normal signature (normal address recovered from normal signature), required !from_self - // type 2: special signature (r == 0), reserved address (stored in s), required from_self + reduce special balance - // type 3: special signature (r == 0), normal address (stored in s), required from_self - if(from_self) { - check(is_special_signature, "actions from self without a special signature are unexpected"); - if (is_reserved_address(*tx.from)) { - const name ingress_account(*extract_reserved_address(*tx.from)); - - const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas; - check(max_gas_cost + tx.value < std::numeric_limits::max(), "too much gas"); - const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost; - - populate_bridge_accessors(); - balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){ - b.balance -= value_with_max_gas; - }); - inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer); + // 1 Reject special signature NOT from self. + // 2 All other cases (normal signature or special signature from self) will be accepted. + // 3 If the from address is reserved address, the special balance needs some process. + check(from_self || !is_special_signature, "bridge signature used outside of bridge transaction"); + + if (is_reserved_address(*tx.from)) { + const name ingress_account(*extract_reserved_address(*tx.from)); - ep.state().set_balance(*tx.from, value_with_max_gas); - ep.state().set_nonce(*tx.from, tx.nonce); - } + const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas; + check(max_gas_cost + tx.value < std::numeric_limits::max(), "too much gas"); + const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost; + + populate_bridge_accessors(); + balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){ + b.balance -= value_with_max_gas; + }); + inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer); + + ep.state().set_balance(*tx.from, value_with_max_gas); + ep.state().set_nonce(*tx.from, tx.nonce); } - else if(is_special_signature) - check(false, "bridge signature used outside of bridge transaction"); - if(enforce_chain_id && !from_self) { + // 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) { check(tx.chain_id.has_value(), "tx without chain-id"); } @@ -277,7 +276,7 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& } if(from_self) - eosio::check(receipt.success, "inline actions must succeed"); + eosio::check(receipt.success, "tx executed inline by contract must succeed"); if(!ep.state().reserved_objects().empty()) { bool non_open_account_sent = false; diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index f1fd36e2..b01f8306 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -155,7 +155,7 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { auto total_fund2 = intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n)); BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 0, "alice"_n, "alice"_n), - eosio_assert_message_exception, eosio_assert_message_is("inline actions must succeed")); + eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); // Call and check results @@ -230,7 +230,7 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 0, evm2, "evm"_n), - eosio_assert_message_exception, eosio_assert_message_is("inline actions must succeed")); + eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); // Call and check results admincall_test(token_addr, 1234, evm2, "evm"_n); From 4a22a2279b91a2277f9db162441cbb1ca507bb9d Mon Sep 17 00:00:00 2001 From: yarkin Date: Wed, 9 Aug 2023 16:49:18 +0800 Subject: [PATCH 14/26] Fix test styles. --- tests/call_tests.cpp | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index b01f8306..b83db0e3 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -119,19 +119,19 @@ struct call_evm_tester : basic_evm_tester { BOOST_AUTO_TEST_SUITE(call_evm_tests) BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { evm_eoa evm1; - auto total_fund = intx::uint256(vault_balance("evm"_n)); + auto total_fund = intx::uint256(vault_balance(evm_account_name)); // Fund evm1 address with 100 EOS - transfer_token("alice"_n, "evm"_n, make_asset(1000000), evm1.address_0x()); + transfer_token("alice"_n, evm_account_name, make_asset(1000000), evm1.address_0x()); auto evm1_balance = evm_balance(evm1); BOOST_REQUIRE(!!evm1_balance); - BOOST_REQUIRE(*evm1_balance == intx::exp<256>(10, 18) * 100); + BOOST_REQUIRE(*evm1_balance == 100_ether); total_fund += *evm1_balance; // Deploy contract auto token_addr = deploy_test_contract(evm1); // Deployment gas fee go to evm vault - BOOST_REQUIRE(*evm_balance(evm1) + intx::uint256(vault_balance("evm"_n)) == total_fund); + BOOST_REQUIRE(*evm_balance(evm1) + intx::uint256(vault_balance(evm_account_name)) == total_fund); // Missing authority BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 1234, "alice"_n, "bob"_n), @@ -149,10 +149,10 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { eosio_assert_message_exception, eosio_assert_message_is("decrementing more than available")); // Transfer enough funds - transfer_token("alice"_n, "evm"_n, make_asset(1000000), "alice"); + transfer_token("alice"_n, evm_account_name, make_asset(1000000), "alice"); - BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == intx::exp<256>(10, 18) * 100); - auto total_fund2 = intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n)); + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == 100_ether); + auto total_fund2 = intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name)); BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 0, "alice"_n, "alice"_n), eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); @@ -164,7 +164,7 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { BOOST_REQUIRE(count == 1234); // Gas go from alice's vault to evm's vault - BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n))); + BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name))); // Advance block so we do not generate same transaction. @@ -175,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { BOOST_REQUIRE(count == 5555); // Gas go from alice's vault to evm's vault - BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance("evm"_n))); + BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name))); // Function being called on behalf of reserved address of eos account "alice" auto caller = get_lastcaller(token_addr); @@ -187,69 +187,69 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { evm_eoa evm1; evm_eoa evm2; - auto total_fund = intx::uint256(vault_balance("evm"_n)); + auto total_fund = intx::uint256(vault_balance(evm_account_name)); // Fund evm1 address with 100 EOS - transfer_token("alice"_n, "evm"_n, make_asset(1000000), evm1.address_0x()); + transfer_token("alice"_n, evm_account_name, make_asset(1000000), evm1.address_0x()); auto evm1_balance = evm_balance(evm1); BOOST_REQUIRE(!!evm1_balance); - BOOST_REQUIRE(*evm1_balance == intx::exp<256>(10, 18) * 100); + BOOST_REQUIRE(*evm1_balance == 100_ether); total_fund += *evm1_balance; // Deploy contract auto token_addr = deploy_test_contract(evm1); // Deployment gas fee go to evm vault - BOOST_REQUIRE(*evm_balance(evm1) + intx::uint256(vault_balance("evm"_n)) == total_fund); + BOOST_REQUIRE(*evm_balance(evm1) + intx::uint256(vault_balance(evm_account_name)) == total_fund); // Missing authority BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 1234, evm2, "alice"_n), missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); // Account not created - BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, "evm"_n), + BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, evm_account_name), eosio_assert_message_exception, eosio_assert_message_is("invalid address")); // Transfer small amount to create account - transfer_token("alice"_n, "evm"_n, make_asset(100), evm2.address_0x()); + transfer_token("alice"_n, evm_account_name, make_asset(100), evm2.address_0x()); auto evm2_balance = evm_balance(evm2); BOOST_REQUIRE(!!evm2_balance); - BOOST_REQUIRE(*evm2_balance == intx::exp<256>(10, 18 - 4) * 100); + BOOST_REQUIRE(*evm2_balance == 10_finney); // Insufficient funds - BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, "evm"_n), + BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, evm_account_name), eosio_assert_message_exception, eosio_assert_message_is("validate_transaction error: 23 Insufficient funds")); // Transfer enough funds - transfer_token("alice"_n, "evm"_n, make_asset(10000), evm2.address_0x()); + transfer_token("alice"_n, evm_account_name, make_asset(10000), evm2.address_0x()); - BOOST_REQUIRE(evm_balance(evm2) == intx::exp<256>(10, 18 - 4) * 10100); - auto total_fund2 = intx::uint256(vault_balance("evm"_n)) + *evm_balance(evm2); + BOOST_REQUIRE(evm_balance(evm2) == 1010_finney); + auto total_fund2 = intx::uint256(vault_balance(evm_account_name)) + *evm_balance(evm2); - BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 0, evm2, "evm"_n), + BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 0, evm2, evm_account_name), eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); // Call and check results - admincall_test(token_addr, 1234, evm2, "evm"_n); + admincall_test(token_addr, 1234, evm2, evm_account_name); auto count = get_count(token_addr); BOOST_REQUIRE(count == 1234); // Gas go from evm2 to evm vault - BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance("evm"_n))); + BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance(evm_account_name))); // Advance block so we do not generate same transaction. produce_block(); - admincall_test(token_addr, 4321, evm2, "evm"_n); + admincall_test(token_addr, 4321, evm2, evm_account_name); count = get_count(token_addr); BOOST_REQUIRE(count == 5555); // Gas go from evm2 to evm vault - BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance("evm"_n))); + BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance(evm_account_name))); // Function being called on behalf of evm address "evm2" auto caller = get_lastcaller(token_addr); @@ -262,7 +262,7 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { BOOST_FIXTURE_TEST_CASE(deploy_contract_function, call_evm_tester) try { auto alice_addr = make_reserved_address("alice"_n.to_uint64_t()); open("alice"_n); - transfer_token("alice"_n, "evm"_n, make_asset(1000000), "alice"); + transfer_token("alice"_n, evm_account_name, make_asset(1000000), "alice"); auto to = evmc::bytes(); @@ -282,7 +282,7 @@ BOOST_FIXTURE_TEST_CASE(deploy_contract_function, call_evm_tester) try { auto from = evmc::bytes{std::begin(alice_addr.bytes), std::end(alice_addr.bytes)}; - admincall(from, to, 0, *data, 1000000, "evm"_n); // nonce 2->3 + admincall(from, to, 0, *data, 1000000, evm_account_name); // nonce 2->3 addr = silkworm::create_address(alice_addr, 2); call_test(addr, 2222, "alice"_n, "alice"_n); // nonce 3->4 From 8156407204a722c094cddba364d1abd6cc70c1a2 Mon Sep 17 00:00:00 2001 From: yarkin Date: Wed, 9 Aug 2023 19:03:01 +0800 Subject: [PATCH 15/26] More detailed tests for gas fee. --- tests/call_tests.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index b83db0e3..9870f810 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -28,6 +28,10 @@ struct call_evm_tester : basic_evm_tester { } } */ + // Cost for first time call to test(), extra cost is needed for the lastcaller storage. + const intx::uint256 gas_fee = suggested_gas_price * 63526; + // Cost for other calls to test() + const intx::uint256 gas_fee2 = suggested_gas_price * 29326; const std::string contract_bytecode = "608060405234801561001057600080fd5b506102ba806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061014c565b60405180910390f35b61007e60048036038101906100799190610198565b6100a4565b005b61008861010d565b6040516100959190610206565b60405180910390f35b60005481565b600081036100b157600080fd5b806000808282546100c29190610250565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61014681610133565b82525050565b6000602082019050610161600083018461013d565b92915050565b600080fd5b61017581610133565b811461018057600080fd5b50565b6000813590506101928161016c565b92915050565b6000602082840312156101ae576101ad610167565b5b60006101bc84828501610183565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101f0826101c5565b9050919050565b610200816101e5565b82525050565b600060208201905061021b60008301846101f7565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061025b82610133565b915061026683610133565b925082820190508082111561027e5761027d610221565b5b9291505056fea2646970667358221220edc009a00fe897643f99e8327c7fb355c96d7b91fb8a7da507513c6b2341acc564736f6c63430008120033"; @@ -162,7 +166,7 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { call_test(token_addr, 1234, "alice"_n, "alice"_n); auto count = get_count(token_addr); BOOST_REQUIRE(count == 1234); - + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == 100_ether - gas_fee); // Gas go from alice's vault to evm's vault BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name))); @@ -173,7 +177,7 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { call_test(token_addr, 4321, "alice"_n, "alice"_n); count = get_count(token_addr); BOOST_REQUIRE(count == 5555); - + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == 100_ether - gas_fee - gas_fee2); // Gas go from alice's vault to evm's vault BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name))); @@ -232,12 +236,13 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 0, evm2, evm_account_name), eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); + // Call and check results admincall_test(token_addr, 1234, evm2, evm_account_name); auto count = get_count(token_addr); BOOST_REQUIRE(count == 1234); - + BOOST_REQUIRE(evm_balance(evm2) == 1010_finney - gas_fee); // Gas go from evm2 to evm vault BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance(evm_account_name))); @@ -247,7 +252,7 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { admincall_test(token_addr, 4321, evm2, evm_account_name); count = get_count(token_addr); BOOST_REQUIRE(count == 5555); - + BOOST_REQUIRE(evm_balance(evm2) == 1010_finney - gas_fee - gas_fee2); // Gas go from evm2 to evm vault BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance(evm_account_name))); From 4b35e5e499221f4d21dccb70d3fe5813f8231f3c Mon Sep 17 00:00:00 2001 From: yarkin Date: Wed, 9 Aug 2023 19:15:38 +0800 Subject: [PATCH 16/26] Update submodule --- silkworm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silkworm b/silkworm index 55a46c32..3f4e257f 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 55a46c321b9cf321729248831956be65067e2fd2 +Subproject commit 3f4e257f582542e1de45e486af33e0f1d48088a0 From 33c066bd1122425184077b74b0b445a36a732d45 Mon Sep 17 00:00:00 2001 From: yarkin Date: Thu, 10 Aug 2023 10:34:46 +0800 Subject: [PATCH 17/26] improve bridge tx handling logic. --- src/actions.cpp | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 91b792c2..60e1f461 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -227,26 +227,28 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& eosio::check(tx.from.has_value(), "unable to recover sender"); LOGTIME("EVM RECOVER SENDER"); - // 1 Reject special signature NOT from self. - // 2 All other cases (normal signature or special signature from self) will be accepted. - // 3 If the from address is reserved address, the special balance needs some process. - check(from_self || !is_special_signature, "bridge signature used outside of bridge transaction"); - - if (is_reserved_address(*tx.from)) { - const name ingress_account(*extract_reserved_address(*tx.from)); - - const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas; - check(max_gas_cost + tx.value < std::numeric_limits::max(), "too much gas"); - const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost; - - populate_bridge_accessors(); - balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){ - b.balance -= value_with_max_gas; - }); - inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer); + // 1 For regular signature, it's impossible to from reserved address, + // and now we accpet them regardless from self or not, so no special treatment. + // 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"); + if (is_reserved_address(*tx.from)) { + const name ingress_account(*extract_reserved_address(*tx.from)); + + const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas; + check(max_gas_cost + tx.value < std::numeric_limits::max(), "too much gas"); + const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost; + + populate_bridge_accessors(); + balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){ + b.balance -= value_with_max_gas; + }); + inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer); - ep.state().set_balance(*tx.from, value_with_max_gas); - ep.state().set_nonce(*tx.from, tx.nonce); + ep.state().set_balance(*tx.from, value_with_max_gas); + ep.state().set_nonce(*tx.from, tx.nonce); + } } // A tx from self with regular signature can potentially from external source. From 5d9afcd340fb5afd371ad453fec3b1378f2ddfcf Mon Sep 17 00:00:00 2001 From: yarkin Date: Thu, 10 Aug 2023 22:24:21 +0800 Subject: [PATCH 18/26] Only intx::uint128's ctor can work properly with uint128_t. We may consider fix that in intx. --- src/actions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 60e1f461..2da82a09 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -594,7 +594,7 @@ void evm_contract::call(eosio::name from, const bytes& to, uint128_t value, cons assert_unfrozen(); require_auth(from); - call_(from.value, to, intx::uint256(value), data, gas_limit, get_and_increment_nonce(from)); + call_(from.value, to, intx::uint256(intx::uint128(value)), data, gas_limit, get_and_increment_nonce(from)); } void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit) { @@ -623,7 +623,7 @@ void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value nonce = account->nonce; } - call_(s, to, intx::uint256(value), data, gas_limit, nonce); + call_(s, to, intx::uint256(intx::uint128(value)), data, gas_limit, nonce); } #ifdef WITH_TEST_ACTIONS From 8101a513d048c376cea6d28c04993580be09cef7 Mon Sep 17 00:00:00 2001 From: yarkin Date: Thu, 10 Aug 2023 22:26:27 +0800 Subject: [PATCH 19/26] Add tests for calls with payments. --- tests/call_tests.cpp | 244 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 211 insertions(+), 33 deletions(-) diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index 9870f810..0384f12d 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -17,23 +17,37 @@ struct call_evm_tester : basic_evm_tester { pragma solidity ^0.8.0; contract Test { - uint256 public count; - address public lastcaller; + uint256 public count; + address public lastcaller; - function test(uint256 input) public { - require(input != 0); + function test(uint256 input) public { + require(input != 0); + + count += input; + lastcaller = msg.sender; + } + + function testpay() payable public { + + } + + function notpayable() public { + + } - count += input; - lastcaller = msg.sender; - } } */ // Cost for first time call to test(), extra cost is needed for the lastcaller storage. const intx::uint256 gas_fee = suggested_gas_price * 63526; // Cost for other calls to test() const intx::uint256 gas_fee2 = suggested_gas_price * 29326; + // Cost for calls to testpay() + const intx::uint256 gas_fee_testpay = suggested_gas_price * 21206; + // Cost for calls to notpayable with 0 value + const intx::uint256 gas_fee_notpayable = suggested_gas_price * 21274; + const std::string contract_bytecode = - "608060405234801561001057600080fd5b506102ba806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806306661abd1461004657806329e99f0714610064578063d097e7a614610080575b600080fd5b61004e61009e565b60405161005b919061014c565b60405180910390f35b61007e60048036038101906100799190610198565b6100a4565b005b61008861010d565b6040516100959190610206565b60405180910390f35b60005481565b600081036100b157600080fd5b806000808282546100c29190610250565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61014681610133565b82525050565b6000602082019050610161600083018461013d565b92915050565b600080fd5b61017581610133565b811461018057600080fd5b50565b6000813590506101928161016c565b92915050565b6000602082840312156101ae576101ad610167565b5b60006101bc84828501610183565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101f0826101c5565b9050919050565b610200816101e5565b82525050565b600060208201905061021b60008301846101f7565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061025b82610133565b915061026683610133565b925082820190508082111561027e5761027d610221565b5b9291505056fea2646970667358221220edc009a00fe897643f99e8327c7fb355c96d7b91fb8a7da507513c6b2341acc564736f6c63430008120033"; + "608060405234801561001057600080fd5b5061030f806100206000396000f3fe60806040526004361061004a5760003560e01c806306661abd1461004f57806329e99f071461007a578063a1a7d817146100a3578063d097e7a6146100ad578063d79e1b6a146100d8575b600080fd5b34801561005b57600080fd5b506100646100ef565b60405161007191906101a1565b60405180910390f35b34801561008657600080fd5b506100a1600480360381019061009c91906101ed565b6100f5565b005b6100ab61015e565b005b3480156100b957600080fd5b506100c2610160565b6040516100cf919061025b565b60405180910390f35b3480156100e457600080fd5b506100ed610186565b005b60005481565b6000810361010257600080fd5b8060008082825461011391906102a5565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b565b6000819050919050565b61019b81610188565b82525050565b60006020820190506101b66000830184610192565b92915050565b600080fd5b6101ca81610188565b81146101d557600080fd5b50565b6000813590506101e7816101c1565b92915050565b600060208284031215610203576102026101bc565b5b6000610211848285016101d8565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102458261021a565b9050919050565b6102558161023a565b82525050565b6000602082019050610270600083018461024c565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102b082610188565b91506102bb83610188565b92508282019050808211156102d3576102d2610276565b5b9291505056fea2646970667358221220ed95d8f74110a8eb6307b7ae52b8623fd3e959169b208830a960c99a9ba1dbf564736f6c63430008120033"; call_evm_tester() { create_accounts({"alice"_n}); @@ -59,6 +73,48 @@ struct call_evm_tester : basic_evm_tester { call(eos, to, 0, data, 500000, actor); } + void call_testpay(const evmc::address& contract_addr, uint128_t amount, name eos, name actor) { + + auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("a1a7d817").value(); // sha3(testpay())[:4] + + call(eos, to, amount, data, 500000, actor); + } + + void call_notpayable(const evmc::address& contract_addr, uint128_t amount, name eos, name actor) { + + auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("d79e1b6a").value(); // sha3(notpayable())[:4] + + call(eos, to, amount, data, 500000, actor); + } + + void admincall_testpay(const evmc::address& contract_addr, uint128_t amount, evm_eoa& eoa, name actor) { + + auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + auto from = evmc::bytes{std::begin(eoa.address.bytes), std::end(eoa.address.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("a1a7d817").value(); // sha3(testpay())[:4] + + admincall(from, to, amount, data, 500000, actor); + } + + void admincall_notpayable(const evmc::address& contract_addr, uint128_t amount, evm_eoa& eoa, name actor) { + + auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + auto from = evmc::bytes{std::begin(eoa.address.bytes), std::end(eoa.address.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("d79e1b6a").value(); // sha3(notpayable())[:4] + + admincall(from, to, amount, data, 500000, actor); + } + void admincall_test(const evmc::address& contract_addr, uint64_t amount, evm_eoa& eoa, name actor) { auto to = evmc::bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; auto from = evmc::bytes{std::begin(eoa.address.bytes), std::end(eoa.address.bytes)}; @@ -152,24 +208,28 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 1234, "alice"_n, "alice"_n), eosio_assert_message_exception, eosio_assert_message_is("decrementing more than available")); - // Transfer enough funds + // Transfer enough funds, save initial balance value. transfer_token("alice"_n, evm_account_name, make_asset(1000000), "alice"); - - BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == 100_ether); - auto total_fund2 = intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name)); + auto alice_balance = 100_ether; + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + auto evm_account_balance = intx::uint256(vault_balance(evm_account_name)); BOOST_REQUIRE_EXCEPTION(call_test(token_addr, 0, "alice"_n, "alice"_n), eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); // Call and check results call_test(token_addr, 1234, "alice"_n, "alice"_n); auto count = get_count(token_addr); BOOST_REQUIRE(count == 1234); - BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == 100_ether - gas_fee); - // Gas go from alice's vault to evm's vault - BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name))); + alice_balance -= gas_fee; + evm_account_balance += gas_fee; + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); // Advance block so we do not generate same transaction. produce_block(); @@ -177,14 +237,73 @@ BOOST_FIXTURE_TEST_CASE(call_test_function, call_evm_tester) try { call_test(token_addr, 4321, "alice"_n, "alice"_n); count = get_count(token_addr); BOOST_REQUIRE(count == 5555); - BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == 100_ether - gas_fee - gas_fee2); - // Gas go from alice's vault to evm's vault - BOOST_REQUIRE(total_fund2 == intx::uint256(vault_balance("alice"_n)) + intx::uint256(vault_balance(evm_account_name))); + + alice_balance -= gas_fee2; + evm_account_balance += gas_fee2; + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); // Function being called on behalf of reserved address of eos account "alice" auto caller = get_lastcaller(token_addr); BOOST_REQUIRE(caller == make_reserved_address("alice"_n.to_uint64_t())); + + BOOST_REQUIRE_EXCEPTION(call_notpayable(token_addr, 100, "alice"_n, "alice"_n), + eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); + + call_notpayable(token_addr, 0, "alice"_n, "alice"_n); + + alice_balance -= gas_fee_notpayable; + evm_account_balance += gas_fee_notpayable; + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); + + call_testpay(token_addr, 0, "alice"_n, "alice"_n); + + alice_balance -= gas_fee_testpay; + evm_account_balance += gas_fee_testpay; + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); + + call_testpay(token_addr, *((uint128_t*)intx::as_words(50_ether)), "alice"_n, "alice"_n); + + alice_balance -= gas_fee_testpay; + alice_balance -= 50_ether; + evm_account_balance += gas_fee_testpay; + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 50_ether); + + // Advance block so we do not generate same transaction. + produce_block(); + + // No enough gas + BOOST_REQUIRE_EXCEPTION(call_testpay(token_addr, *((uint128_t*)intx::as_words(50_ether)), "alice"_n, "alice"_n), + eosio_assert_message_exception, eosio_assert_message_is("decrementing more than available")); + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 50_ether); + + call_testpay(token_addr, *((uint128_t*)intx::as_words(10_ether)), "alice"_n, "alice"_n); + + alice_balance -= gas_fee_testpay; + alice_balance -= 10_ether; + evm_account_balance += gas_fee_testpay; + + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == alice_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 60_ether); } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { @@ -218,20 +337,19 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { // Transfer small amount to create account transfer_token("alice"_n, evm_account_name, make_asset(100), evm2.address_0x()); - auto evm2_balance = evm_balance(evm2); - BOOST_REQUIRE(!!evm2_balance); - BOOST_REQUIRE(*evm2_balance == 10_finney); + auto tb = evm_balance(evm2); + BOOST_REQUIRE(!!tb); + BOOST_REQUIRE(*tb == 10_finney); // Insufficient funds BOOST_REQUIRE_EXCEPTION( admincall_test(token_addr, 1234, evm2, evm_account_name), eosio_assert_message_exception, eosio_assert_message_is("validate_transaction error: 23 Insufficient funds")); - // Transfer enough funds - transfer_token("alice"_n, evm_account_name, make_asset(10000), evm2.address_0x()); - - BOOST_REQUIRE(evm_balance(evm2) == 1010_finney); - auto total_fund2 = intx::uint256(vault_balance(evm_account_name)) + *evm_balance(evm2); - + // Transfer enough funds, save initial balance + transfer_token("alice"_n, evm_account_name, make_asset(999900), evm2.address_0x()); + auto evm2_balance = 100_ether; + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + auto evm_account_balance = intx::uint256(vault_balance(evm_account_name)); BOOST_REQUIRE_EXCEPTION(admincall_test(token_addr, 0, evm2, evm_account_name), eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); @@ -242,9 +360,12 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { auto count = get_count(token_addr); BOOST_REQUIRE(count == 1234); - BOOST_REQUIRE(evm_balance(evm2) == 1010_finney - gas_fee); - // Gas go from evm2 to evm vault - BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance(evm_account_name))); + + evm2_balance -= gas_fee; + evm_account_balance += gas_fee; + + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); // Advance block so we do not generate same transaction. produce_block(); @@ -252,14 +373,71 @@ BOOST_FIXTURE_TEST_CASE(admincall_test_function, call_evm_tester) try { admincall_test(token_addr, 4321, evm2, evm_account_name); count = get_count(token_addr); BOOST_REQUIRE(count == 5555); - BOOST_REQUIRE(evm_balance(evm2) == 1010_finney - gas_fee - gas_fee2); - // Gas go from evm2 to evm vault - BOOST_REQUIRE(total_fund2 == *evm_balance(evm2) + intx::uint256(vault_balance(evm_account_name))); + + evm2_balance -= gas_fee2; + evm_account_balance += gas_fee2; + + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); // Function being called on behalf of evm address "evm2" auto caller = get_lastcaller(token_addr); BOOST_REQUIRE(caller== evm2.address); + + BOOST_REQUIRE_EXCEPTION(admincall_notpayable(token_addr, 100, evm2, evm_account_name), + eosio_assert_message_exception, eosio_assert_message_is("tx executed inline by contract must succeed")); + + BOOST_REQUIRE(evm_balance(evm2)== evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); + + admincall_notpayable(token_addr, 0, evm2, evm_account_name); + + evm2_balance -= gas_fee_notpayable; + evm_account_balance += gas_fee_notpayable; + + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); + + admincall_testpay(token_addr, 0, evm2, evm_account_name); + + evm2_balance -= gas_fee_testpay; + evm_account_balance += gas_fee_testpay; + + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 0); + + admincall_testpay(token_addr, *((uint128_t*)intx::as_words(50_ether)), evm2, evm_account_name); + + evm2_balance -= gas_fee_testpay; + evm2_balance -= 50_ether; + evm_account_balance += gas_fee_testpay; + + BOOST_REQUIRE(evm_balance(evm2)== evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 50_ether); + + // Advance block so we do not generate same transaction. + produce_block(); + + // No enough gas + BOOST_REQUIRE_EXCEPTION(admincall_testpay(token_addr, *((uint128_t*)intx::as_words(50_ether)), evm2, evm_account_name), + eosio_assert_message_exception, eosio_assert_message_is("validate_transaction error: 23 Insufficient funds")); + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 50_ether); + + admincall_testpay(token_addr, *((uint128_t*)intx::as_words(10_ether)), evm2, evm_account_name); + + evm2_balance -= gas_fee_testpay; + evm2_balance -= 10_ether; + evm_account_balance += gas_fee_testpay; + BOOST_REQUIRE(evm_balance(evm2) == evm2_balance); + BOOST_REQUIRE(intx::uint256(vault_balance(evm_account_name)) == evm_account_balance); + BOOST_REQUIRE(*evm_balance(token_addr) == 60_ether); } FC_LOG_AND_RETHROW() From 9759072f6233f67e04474ca469160fbb5b8ad18a Mon Sep 17 00:00:00 2001 From: yarkin Date: Fri, 11 Aug 2023 12:55:55 +0800 Subject: [PATCH 20/26] Change data type of value to const byte& --- include/evm_runtime/evm_contract.hpp | 4 +-- src/actions.cpp | 16 +++++++++--- tests/basic_evm_tester.cpp | 16 +++++++++--- tests/basic_evm_tester.hpp | 4 +-- tests/call_tests.cpp | 38 +++++++++++++++++++++------- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index cd737d10..e52669fa 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -85,8 +85,8 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] bool gc(uint32_t max); - [[eosio::action]] void call(eosio::name from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit); - [[eosio::action]] void admincall(const bytes& from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit); + [[eosio::action]] void call(eosio::name from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit); + [[eosio::action]] void admincall(const bytes& from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit); #ifdef WITH_TEST_ACTIONS [[eosio::action]] void testtx(const std::optional& orlptx, const evm_runtime::test::block_info& bi); diff --git a/src/actions.cpp b/src/actions.cpp index 2da82a09..11ee59d6 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -590,16 +590,24 @@ void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, pushtx_act.send(get_self(), rlp); } -void evm_contract::call(eosio::name from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit) { +void evm_contract::call(eosio::name from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) { assert_unfrozen(); require_auth(from); - call_(from.value, to, intx::uint256(intx::uint128(value)), data, gas_limit, get_and_increment_nonce(from)); + // Prepare v + 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)); } -void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit) { +void evm_contract::admincall(const bytes& from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) { assert_unfrozen(); require_auth(get_self()); + + // Prepare v + eosio::check(value.size() == sizeof(intx::uint256), "invalid value"); + intx::uint256 v = intx::be::unsafe::load((const uint8_t *)value.data()); // Prepare s eosio::check(from.size() == kAddressLength, err_msg_invalid_addr); @@ -623,7 +631,7 @@ void evm_contract::admincall(const bytes& from, const bytes& to, uint128_t value nonce = account->nonce; } - call_(s, to, intx::uint256(intx::uint128(value)), data, gas_limit, nonce); + call_(s, to, v, data, gas_limit, nonce); } #ifdef WITH_TEST_ACTIONS diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index 649486c3..5f7121ca 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -291,7 +291,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, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor) +void 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()); @@ -301,10 +301,14 @@ void basic_evm_tester::call(name from, const evmc::bytes& to, uint128_t value, e data_bytes.resize(data.size()); memcpy(data_bytes.data(), data.data(), data.size()); - push_action(evm_account_name, "call"_n, actor, mvo()("from", from)("to", to_bytes)("value", value)("data", data_bytes)("gas_limit", gas_limit)); + bytes value_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)); } -void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor) +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) { bytes to_bytes; to_bytes.resize(to.size()); @@ -318,7 +322,11 @@ void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, from_bytes.resize(from.size()); memcpy(from_bytes.data(), from.data(), from.size()); - push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value)("data", data_bytes)("gas_limit", gas_limit)); + bytes value_bytes; + 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)); } void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index d2c3fa16..64303da7 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -200,8 +200,8 @@ class basic_evm_tester : public testing::validating_tester transaction_trace_ptr exec(const exec_input& input, const std::optional& callback); void pushtx(const silkworm::Transaction& trx, name miner = evm_account_name); - void call(name from, const evmc::bytes& to, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor); - void admincall(const evmc::bytes& from, const evmc::bytes& to, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor); + 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); evmc::address deploy_contract(evm_eoa& eoa, evmc::bytes bytecode); void addegress(const std::vector& accounts); diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp index 0384f12d..085ea3f2 100644 --- a/tests/call_tests.cpp +++ b/tests/call_tests.cpp @@ -70,7 +70,10 @@ struct call_evm_tester : basic_evm_tester { data += evmc::from_hex("29e99f07").value(); // sha3(test(uint256))[:4] data += evmc::bytes32{amount}; // value - call(eos, to, 0, data, 500000, actor); + evmc::bytes32 v; + intx::be::store(v.bytes, intx::uint256(0)); + + call(eos, to, silkworm::Bytes(v), data, 500000, actor); } void call_testpay(const evmc::address& contract_addr, uint128_t amount, name eos, name actor) { @@ -80,7 +83,10 @@ struct call_evm_tester : basic_evm_tester { silkworm::Bytes data; data += evmc::from_hex("a1a7d817").value(); // sha3(testpay())[:4] - call(eos, to, amount, data, 500000, actor); + evmc::bytes32 v; + intx::be::store(v.bytes, intx::uint256(intx::uint128(amount))); + + call(eos, to, silkworm::Bytes(v), data, 500000, actor); } void call_notpayable(const evmc::address& contract_addr, uint128_t amount, name eos, name actor) { @@ -90,7 +96,10 @@ struct call_evm_tester : basic_evm_tester { silkworm::Bytes data; data += evmc::from_hex("d79e1b6a").value(); // sha3(notpayable())[:4] - call(eos, to, amount, data, 500000, actor); + evmc::bytes32 v; + intx::be::store(v.bytes, intx::uint256(intx::uint128(amount))); + + call(eos, to, silkworm::Bytes(v), data, 500000, actor); } void admincall_testpay(const evmc::address& contract_addr, uint128_t amount, evm_eoa& eoa, name actor) { @@ -101,7 +110,10 @@ struct call_evm_tester : basic_evm_tester { silkworm::Bytes data; data += evmc::from_hex("a1a7d817").value(); // sha3(testpay())[:4] - admincall(from, to, amount, data, 500000, actor); + evmc::bytes32 v; + intx::be::store(v.bytes, intx::uint256(intx::uint128(amount))); + + admincall(from, to, silkworm::Bytes(v), data, 500000, actor); } void admincall_notpayable(const evmc::address& contract_addr, uint128_t amount, evm_eoa& eoa, name actor) { @@ -112,7 +124,10 @@ struct call_evm_tester : basic_evm_tester { silkworm::Bytes data; data += evmc::from_hex("d79e1b6a").value(); // sha3(notpayable())[:4] - admincall(from, to, amount, data, 500000, actor); + evmc::bytes32 v; + intx::be::store(v.bytes, intx::uint256(intx::uint128(amount))); + + admincall(from, to, silkworm::Bytes(v), data, 500000, actor); } void admincall_test(const evmc::address& contract_addr, uint64_t amount, evm_eoa& eoa, name actor) { @@ -122,7 +137,10 @@ struct call_evm_tester : basic_evm_tester { data += evmc::from_hex("29e99f07").value(); // sha3(test(uint256))[:4] data += evmc::bytes32{amount}; // value - admincall(from, to, 0, data, 500000, actor); + evmc::bytes32 v; + intx::be::store(v.bytes, intx::uint256(0)); + + admincall(from, to, silkworm::Bytes(v), data, 500000, actor); } intx::uint256 get_count(const evmc::address& contract_addr, std::optional callback={}, std::optional context={}) { @@ -446,13 +464,15 @@ BOOST_FIXTURE_TEST_CASE(deploy_contract_function, call_evm_tester) try { auto alice_addr = make_reserved_address("alice"_n.to_uint64_t()); open("alice"_n); transfer_token("alice"_n, evm_account_name, make_asset(1000000), "alice"); - + + + evmc::bytes32 v; auto to = evmc::bytes(); auto data = evmc::from_hex(contract_bytecode); - call("alice"_n, to, 0, *data, 1000000, "alice"_n); // nonce 0->1 + call("alice"_n, to, silkworm::Bytes(v), *data, 1000000, "alice"_n); // nonce 0->1 auto addr = silkworm::create_address(alice_addr, 0); @@ -465,7 +485,7 @@ BOOST_FIXTURE_TEST_CASE(deploy_contract_function, call_evm_tester) try { auto from = evmc::bytes{std::begin(alice_addr.bytes), std::end(alice_addr.bytes)}; - admincall(from, to, 0, *data, 1000000, evm_account_name); // nonce 2->3 + admincall(from, to, silkworm::Bytes(v), *data, 1000000, evm_account_name); // nonce 2->3 addr = silkworm::create_address(alice_addr, 2); call_test(addr, 2222, "alice"_n, "alice"_n); // nonce 3->4 From 37b8ee22c8f205cec922ad40d3fb0c15652f0a4a Mon Sep 17 00:00:00 2001 From: yarkin Date: Mon, 14 Aug 2023 23:02:34 +0800 Subject: [PATCH 21/26] Fix an unsafe intx::load. --- src/actions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 11ee59d6..116bf5c9 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -611,9 +611,9 @@ void evm_contract::admincall(const bytes& from, const bytes& to, const bytes& va // Prepare s eosio::check(from.size() == kAddressLength, err_msg_invalid_addr); - intx::uint256 s = intx::be::unsafe::load((const uint8_t *)from.data()); - // load will put the data in higher bytes, shift them donw. - s >>= 256 - kAddressLength * 8; + uint8_t s_buffer[32] = {}; + memcpy(s_buffer + 32 - kAddressLength, from.data(), kAddressLength); + intx::uint256 s = intx::be::load(s_buffer); // pad with '1's s |= ((~intx::uint256(0)) << (kAddressLength * 8)); From 31d70247d54bc3bd0dc3ecab5f629c4a801b2282 Mon Sep 17 00:00:00 2001 From: Areg Hayrapetian Date: Mon, 14 Aug 2023 14:21:10 -0700 Subject: [PATCH 22/26] Update silkworm submodule --- silkworm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silkworm b/silkworm index 3f4e257f..d661be9a 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 3f4e257f582542e1de45e486af33e0f1d48088a0 +Subproject commit d661be9a624505fb769e9c964ab5ba995e56c94a From f506abeeed26d52eaef5ec2fb9a8f6d7959f93be Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 16 Aug 2023 03:13:23 -0300 Subject: [PATCH 23/26] tests: move inevm function to base tester class --- tests/basic_evm_tester.cpp | 4 ++++ tests/basic_evm_tester.hpp | 1 + tests/native_token_tester.hpp | 5 ----- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index 628137ff..c0b7121f 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -358,6 +358,10 @@ void basic_evm_tester::withdraw(name owner, asset quantity) push_action(evm_account_name, "withdraw"_n, owner, mvo()("owner", owner)("quantity", quantity)); } +balance_and_dust basic_evm_tester::inevm() const { + return fc::raw::unpack(get_row_by_account(evm_account_name, evm_account_name, "inevm"_n, "inevm"_n)); +} + balance_and_dust basic_evm_tester::vault_balance(name owner) const { const vector d = get_row_by_account(evm_account_name, evm_account_name, "balances"_n, owner); diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 702f0a60..a5b5545f 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -228,6 +228,7 @@ class basic_evm_tester : public testing::validating_tester void close(name owner); void withdraw(name owner, asset quantity); + balance_and_dust inevm() const; balance_and_dust vault_balance(name owner) const; std::optional evm_balance(const evmc::address& address) const; std::optional evm_balance(const evm_eoa& account) const; diff --git a/tests/native_token_tester.hpp b/tests/native_token_tester.hpp index 720386ec..2f8b267f 100644 --- a/tests/native_token_tester.hpp +++ b/tests/native_token_tester.hpp @@ -51,11 +51,6 @@ struct native_token_evm_tester : basic_evm_tester { uint64_t vault_balance_dust(name owner) const { return vault_balance(owner).dust; } - - balance_and_dust inevm() const - { - return fc::raw::unpack(get_row_by_account("evm"_n, "evm"_n, "inevm"_n, "inevm"_n)); - } }; struct native_token_evm_tester_EOS : native_token_evm_tester { From 76319e7f044a02aaf66ba685404d79cb4ccf241a Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 16 Aug 2023 19:22:35 -0300 Subject: [PATCH 24/26] Remove balance modification for the EOS EVM contract account The accounting is already being done in the execute_tx function when substracting all the final balance of any reserved address --- src/actions.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index df930642..a83eb009 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -452,11 +452,6 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { balance_table.modify(receiver_account, eosio::same_payer, [&](balance& row) { row.balance += value; }); - - const balance& self_account = balance_table.get(get_self().value, "self account is not open"); - balance_table.modify(self_account, eosio::same_payer, [&](balance& row) { - row.balance -= value; - }); } engine.finalize(ep.state(), ep.evm().block()); From 656706e986777898ffa767c4dc60b386622d9599 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 16 Aug 2023 19:28:30 -0300 Subject: [PATCH 25/26] Add versioning to the bridge message --- include/evm_runtime/bridge.hpp | 14 +- include/evm_runtime/types.hpp | 6 +- src/actions.cpp | 30 ++-- tests/CMakeLists.txt | 2 +- tests/basic_evm_tester.hpp | 6 +- ...mit_tests.cpp => bridge_message_tests.cpp} | 141 +++++++++++------- .../evm_bridge_receiver.abi | 26 +++- .../evm_bridge_receiver.cpp | 23 +-- .../evm_bridge_receiver.hpp | 16 +- .../evm_bridge_receiver.wasm | Bin 3112 -> 3685 bytes 10 files changed, 155 insertions(+), 109 deletions(-) rename tests/{bridge_emit_tests.cpp => bridge_message_tests.cpp} (74%) diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp index ea3a0ae3..17fb3e42 100644 --- a/include/evm_runtime/bridge.hpp +++ b/include/evm_runtime/bridge.hpp @@ -4,8 +4,8 @@ namespace evm_runtime { namespace bridge { -struct emit { - static constexpr uint32_t id = 0x44282a35; //sha3('emit_(string,bytes)')[:4] +struct message_v0 { + static constexpr uint32_t id = 0x24578ea5; //sha3('bridgeMsgV0(string,bytes)')[:4] string account; bytes data; @@ -21,9 +21,9 @@ struct emit { mutable std::optional account_; }; -using call = std::variant; +using message = std::variant; -std::optional decode_emit_message(eosio::datastream& ds) { +message_v0 decode_message_v0(eosio::datastream& ds) { // offset_p1 (32) + offset_p2 (32) // p1_len (32) + p1_data ((p1_len+31)/32*32) // p2_len (32) + p1_data ((p2_len+31)/32*32) @@ -35,7 +35,7 @@ std::optional decode_emit_message(eosio::datastream& ds) { ds >> offset_p2; eosio::check(offset_p2 == 0x80, "invalid p2 offset"); - emit res; + message_v0 res; auto get_length=[&]() -> uint32_t { uint256 len; @@ -59,13 +59,13 @@ std::optional decode_emit_message(eosio::datastream& ds) { return res; } -std::optional decode_call_message(ByteView bv) { +std::optional decode_message(ByteView bv) { // method_id (4) eosio::datastream ds(bv.data(), bv.size()); uint32_t method_id; ds >> method_id; - if(method_id == __builtin_bswap32(emit::id)) return decode_emit_message(ds); + if(method_id == __builtin_bswap32(message_v0::id)) return decode_message_v0(ds); return {}; } diff --git a/include/evm_runtime/types.hpp b/include/evm_runtime/types.hpp index 09605bff..50a6293c 100644 --- a/include/evm_runtime/types.hpp +++ b/include/evm_runtime/types.hpp @@ -63,16 +63,18 @@ namespace evm_runtime { EOSLIB_SERIALIZE(exec_output, (status)(data)(context)); }; - struct bridge_emit_message { + struct bridge_message_v0 { eosio::name receiver; bytes sender; eosio::time_point timestamp; bytes value; bytes data; - EOSLIB_SERIALIZE(bridge_emit_message, (receiver)(sender)(timestamp)(value)(data)); + EOSLIB_SERIALIZE(bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); }; + using bridge_message = std::variant; + } //namespace evm_runtime namespace eosio { diff --git a/src/actions.cpp b/src/actions.cpp index a83eb009..b2231d08 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -410,20 +410,20 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { // Filter EVM messages (with data) that are sent to the reserved address // corresponding to the EOS account holding the contract (self) ep.set_evm_message_filter([&](const evmc_message& message) -> bool { - auto me = make_reserved_address(get_self().value); + static auto me = make_reserved_address(get_self().value); return message.recipient == me && message.input_size > 0; }); auto receipt = execute_tx(miner, block, tx, ep, true); - for(const auto& msg : ep.state().filtered_messages()) { + for(const auto& rawmsg : ep.state().filtered_messages()) { - auto call = bridge::decode_call_message(ByteView{msg.data}); - eosio::check(call.has_value(), "unable to decode call message"); + auto msg = bridge::decode_message(ByteView{rawmsg.data}); + eosio::check(msg.has_value(), "unable to decode bridge message"); - auto& emit = std::get(call.value()); + auto& msg_v0 = std::get(msg.value()); - const auto& receiver = emit.get_account_as_name(); + const auto& receiver = msg_v0.get_account_as_name(); eosio::check(eosio::is_account(receiver), "receiver is not account"); message_receiver_table message_receivers(get_self(), get_self().value); @@ -433,20 +433,20 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { intx::uint256 min_fee((uint64_t)it->min_fee.amount); min_fee *= minimum_natively_representable; - auto value = intx::be::unsafe::load(msg.value.bytes); + auto value = intx::be::unsafe::load(rawmsg.value.bytes); eosio::check(value >= min_fee, "min_fee not covered"); balances balance_table(get_self(), get_self().value); - const balance& receiver_account = balance_table.get(emit.get_account_as_name().value, "receiver account is not open"); + const balance& receiver_account = balance_table.get(msg_v0.get_account_as_name().value, "receiver account is not open"); - action(std::vector{}, emit.get_account_as_name(), "onbridgemsg"_n, - bridge_emit_message{ - .receiver = emit.get_account_as_name(), - .sender = to_bytes(msg.sender), + action(std::vector{}, msg_v0.get_account_as_name(), "onbridgemsg"_n, + bridge_message{ bridge_message_v0 { + .receiver = msg_v0.get_account_as_name(), + .sender = to_bytes(rawmsg.sender), .timestamp = eosio::current_time_point(), - .value = to_bytes(msg.value), - .data = emit.data - } + .value = to_bytes(rawmsg.value), + .data = msg_v0.data + } } ).send(); balance_table.modify(receiver_account, eosio::same_payer, [&](balance& row) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 715fa48c..b8e43873 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -31,7 +31,7 @@ add_eosio_test_executable( unit_test ${CMAKE_SOURCE_DIR}/blockhash_tests.cpp ${CMAKE_SOURCE_DIR}/exec_tests.cpp ${CMAKE_SOURCE_DIR}/chainid_tests.cpp - ${CMAKE_SOURCE_DIR}/bridge_emit_tests.cpp + ${CMAKE_SOURCE_DIR}/bridge_message_tests.cpp ${CMAKE_SOURCE_DIR}/main.cpp ${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/encode.cpp ${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/decode.cpp diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index a5b5545f..213c8236 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -114,7 +114,7 @@ struct message_receiver { asset min_fee; }; -struct bridge_emit_message { +struct bridge_message_v0 { name receiver; bytes sender; time_point timestamp; @@ -122,6 +122,8 @@ struct bridge_emit_message { bytes data; }; +using bridge_message = std::variant; + } // namespace evm_test @@ -137,7 +139,7 @@ FC_REFLECT(evm_test::exec_callback, (contract)(action)) FC_REFLECT(evm_test::exec_output, (status)(data)(context)) FC_REFLECT(evm_test::message_receiver, (account)(min_fee)); -FC_REFLECT(evm_test::bridge_emit_message, (receiver)(sender)(timestamp)(value)(data)); +FC_REFLECT(evm_test::bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); namespace evm_test { class evm_eoa diff --git a/tests/bridge_emit_tests.cpp b/tests/bridge_message_tests.cpp similarity index 74% rename from tests/bridge_emit_tests.cpp rename to tests/bridge_message_tests.cpp index 9c8c4ea8..42d3e774 100644 --- a/tests/bridge_emit_tests.cpp +++ b/tests/bridge_message_tests.cpp @@ -9,21 +9,22 @@ using namespace evm_test; using eosio::testing::eosio_assert_message_is; using eosio::testing::expect_assert_message; -struct bridge_emit_evm_tester : basic_evm_tester { - bridge_emit_evm_tester() { +struct bridge_message_tester : basic_evm_tester { + bridge_message_tester() { create_accounts({"alice"_n}); transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); init(); + open("alice"_n); } std::string int_str32(uint32_t x) { - std::stringstream hex_ss; - hex_ss << std::hex << x; - int hex_length = hex_ss.str().length(); + std::stringstream hex_ss; + hex_ss << std::hex << x; + int hex_length = hex_ss.str().length(); - std::stringstream ss; - ss << std::setfill('0') << std::setw(64 - hex_length) << 0 << std::hex << std::uppercase << x; - return ss.str(); + std::stringstream ss; + ss << std::setfill('0') << std::setw(64 - hex_length) << 0 << std::hex << std::uppercase << x; + return ss.str(); } std::string str_to_hex(const std::string& str) { @@ -35,18 +36,18 @@ struct bridge_emit_evm_tester : basic_evm_tester { } std::string data_str32(const std::string& str) { - std::stringstream ss; - ss << str; - int ps = 64 - (str.length() % 64); - if (ps == 64) {ps = 0;} - ss << std::setw(ps) << std::setfill('0') << ""; - return ss.str(); + std::stringstream ss; + ss << str; + int ps = 64 - (str.length() % 64); + if (ps == 64) {ps = 0;} + ss << std::setw(ps) << std::setfill('0') << ""; + return ss.str(); } - transaction_trace_ptr send_emit_message(evm_eoa& eoa, const std::string& receiver, const intx::uint256& value, const std::string& str_data) { + transaction_trace_ptr send_bridge_message(evm_eoa& eoa, const std::string& receiver, const intx::uint256& value, const std::string& str_data) { silkworm::Bytes data; - data += evmc::from_hex("44282a35").value(); + data += evmc::from_hex("24578ea5").value(); data += evmc::from_hex(int_str32(64)).value(); data += evmc::from_hex(int_str32(128)).value(); data += evmc::from_hex(int_str32(receiver.length())).value(); @@ -54,22 +55,22 @@ struct bridge_emit_evm_tester : basic_evm_tester { data += evmc::from_hex(int_str32(str_data.size()/2)).value(); data += evmc::from_hex(data_str32(str_data)).value(); - return send_raw_message(eoa, make_reserved_address("evm"_n), value, data); + return send_raw_message(eoa, make_reserved_address(evm_account_name), value, data); } transaction_trace_ptr send_raw_message(evm_eoa& eoa, const evmc::address& dest, const intx::uint256& value, const silkworm::Bytes& data) { auto txn = generate_tx(dest, value, 250'000); txn.data = data; eoa.sign(txn); - return pushtx(txn); + return pushtx(txn, "alice"_n); } }; -BOOST_AUTO_TEST_SUITE(bridge_emit_tests) -BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_emit_evm_tester) try { +BOOST_AUTO_TEST_SUITE(bridge_message_tests) +BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_message_tester) try { create_accounts({"rec1"_n}); - + // evm auth is needed BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, make_asset(-1), {}), missing_auth_exception, [](const missing_auth_exception& e) { return expect_assert_message(e, "missing authority of evm"); }); @@ -84,20 +85,20 @@ BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_emit_evm_tester) try { bridgereg("rec1"_n, make_asset(0)); - auto row = fc::raw::unpack(get_row_by_account( "evm"_n, "evm"_n, "msgreceiver"_n, "rec1"_n)); + auto row = fc::raw::unpack(get_row_by_account( evm_account_name, evm_account_name, "msgreceiver"_n, "rec1"_n)); BOOST_REQUIRE(row.account == "rec1"_n); BOOST_REQUIRE(row.min_fee == make_asset(0)); // Register again changing min fee bridgereg("rec1"_n, make_asset(1)); - row = fc::raw::unpack(get_row_by_account( "evm"_n, "evm"_n, "msgreceiver"_n, "rec1"_n)); + row = fc::raw::unpack(get_row_by_account( evm_account_name, evm_account_name, "msgreceiver"_n, "rec1"_n)); BOOST_REQUIRE(row.account == "rec1"_n); BOOST_REQUIRE(row.min_fee == make_asset(1)); // Unregister rec1 bridgeunreg("rec1"_n); - const auto& d = get_row_by_account( "evm"_n, "evm"_n, "msgreceiver"_n, "rec1"_n); + const auto& d = get_row_by_account( evm_account_name, evm_account_name, "msgreceiver"_n, "rec1"_n); BOOST_REQUIRE(d.size() == 0); produce_block(); @@ -107,44 +108,67 @@ BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_emit_evm_tester) try { } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE(basic_tests, bridge_emit_evm_tester) try { +BOOST_FIXTURE_TEST_CASE(basic_tests, bridge_message_tester) try { // Create destination account create_accounts({"rec1"_n}); set_code("rec1"_n, testing::contracts::evm_bridge_receiver_wasm()); set_abi("rec1"_n, testing::contracts::evm_bridge_receiver_abi().data()); - // Register rec1 with 1.0000 EOS as min_fee - bridgereg("rec1"_n, make_asset(10000)); + // Register rec1 with 1000.0000 EOS as min_fee + bridgereg("rec1"_n, make_asset(10000000)); - // Fund evm1 address with 100 EOS + // Fund evm1 address with 1001 EOS evm_eoa evm1; - const int64_t to_bridge = 1000000; - transfer_token("alice"_n, "evm"_n, make_asset(to_bridge), evm1.address_0x()); + const int64_t to_bridge = 10010000; + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + // Get `rec1` and contract balance before sending the message + auto pre_evm_balance = inevm(); + auto pre_rec1_balance = vault_balance("rec1"_n); + BOOST_CHECK(pre_rec1_balance.balance == make_asset(0)); // 0.0000 EOS + BOOST_CHECK(pre_rec1_balance.dust == 0); // Emit message - auto res = send_emit_message(evm1, "rec1", 1_ether, "0102030405060708090a"); + auto res = send_bridge_message(evm1, "rec1", 1000*1_ether, "0102030405060708090a"); + + // Get `rec1` and contract balance after sending the message + auto post_evm_balance = inevm(); + auto post_rec1_balance = vault_balance("rec1"_n); + BOOST_CHECK(post_rec1_balance.balance == make_asset(10000000)); //1000.0000 EOS + BOOST_CHECK(post_rec1_balance.dust == 0); + + // The data length of the bridge message transaction is 196 bytes long + // 22 non zero, 174 zero => total gas is: 21000 + 174*4+22*16 + auto total_gas_fee = intx::uint256(suggested_gas_price)*intx::uint256(21000+174*4+22*16); + dlog("totalgas: ${t}", ("t",total_gas_fee)); + + // Check that the EOS `out` from the EVM is equal to the value sent in the bridge transaction (goes to rec1) + // plus the transaction fee (goes to miner) + auto diff = static_cast(pre_evm_balance)-static_cast(post_evm_balance); + BOOST_CHECK_EQUAL(diff, 1000*1_ether + total_gas_fee); // Recover message form the return value of rec1 contract BOOST_CHECK(res->action_traces[1].receiver == "rec1"_n); - auto out = fc::raw::unpack(res->action_traces[1].return_value); + auto msgout = fc::raw::unpack(res->action_traces[1].return_value); + auto out = std::get(msgout); BOOST_CHECK(out.receiver == "rec1"_n); BOOST_CHECK(out.sender == to_bytes(evm1.address)); BOOST_CHECK(out.timestamp.time_since_epoch() == control->pending_block_time().time_since_epoch()); - BOOST_CHECK(out.value == to_bytes(1_ether)); + BOOST_CHECK(out.value == to_bytes(1000*1_ether)); BOOST_CHECK(out.data == to_bytes(evmc::from_hex("0102030405060708090a").value())); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { +BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { - auto emv_reserved_address = make_reserved_address("evm"_n); + auto emv_reserved_address = make_reserved_address(evm_account_name); // Fund evm1 address with 100 EOS evm_eoa evm1; const int64_t to_bridge = 1000000; - transfer_token("alice"_n, "evm"_n, make_asset(to_bridge), evm1.address_0x()); + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); // Send message without call data [ok] send_raw_message(evm1, emv_reserved_address, 0, silkworm::Bytes{}); @@ -154,25 +178,25 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); evm1.next_nonce--; - // Send message with 4 bytes NOT matching the 'emit_(string,bytes)' signature + // Send message with 4 bytes NOT matching the 'bridgeMsgV0(string,bytes)' signature BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("01020304").value()), - eosio_assert_message_exception, eosio_assert_message_is("unable to decode call message")); + eosio_assert_message_exception, eosio_assert_message_is("unable to decode bridge message")); evm1.next_nonce--; - // Send message with 4 bytes matching the 'emit_(string,bytes)' signature - BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("44282a35").value()), + // Send message with 4 bytes matching the 'bridgeMsgV0(string,bytes)' signature + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("24578ea5").value()), eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); evm1.next_nonce--; // Wrong p1 offset - BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("44282a35").value() + + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("24578ea5").value() + evmc::from_hex(int_str32(11)).value()), eosio_assert_message_exception, eosio_assert_message_is("invalid p1 offset")); evm1.next_nonce--; // Wrong p2 offset BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("44282a35").value() + + evmc::from_hex("24578ea5").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(11)).value()), eosio_assert_message_exception, eosio_assert_message_is("invalid p2 offset")); @@ -180,7 +204,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // abcd is not an account BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("44282a35").value() + + evmc::from_hex("24578ea5").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -195,7 +219,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // abcd not registered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("44282a35").value() + + evmc::from_hex("24578ea5").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -210,7 +234,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // min fee not covered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("44282a35").value() + + evmc::from_hex("24578ea5").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -225,7 +249,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { // receiver account is not open BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 1e14, - evmc::from_hex("44282a35").value() + + evmc::from_hex("24578ea5").value() + evmc::from_hex(int_str32(64)).value() + evmc::from_hex(int_str32(128)).value() + evmc::from_hex(int_str32(4)).value() + @@ -236,7 +260,7 @@ BOOST_FIXTURE_TEST_CASE(test_emit_errors, bridge_emit_evm_tester) try { } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE(emit_from_solidity, bridge_emit_evm_tester) try { +BOOST_FIXTURE_TEST_CASE(test_send_message_from_solidity, bridge_message_tester) try { // // SPDX-License-Identifier: GPL-3.0 // pragma solidity >=0.7.0 <0.9.0; // contract Emiter { @@ -244,12 +268,12 @@ BOOST_FIXTURE_TEST_CASE(emit_from_solidity, bridge_emit_evm_tester) try { // address eosevm = 0xBbBBbbBBbBbbbBBBbBbbBBbB56E4000000000000; // for(uint i=0; i start('rec1', 3) + // Call method "go" on emiter contract (sha3('go(string,uint)') = 0xa5cc93e4) + // ===> go('rec1', 3) auto txn = generate_tx(contract_addr, 0, 500'000); txn.data = evmc::from_hex("a5cc93e4").value(); - txn.data += evmc::from_hex("0000000000000000000000000000000000000000000000000000000000000040").value(); - txn.data += evmc::from_hex("0000000000000000000000000000000000000000000000000000000000000003").value(); - txn.data += evmc::from_hex("0000000000000000000000000000000000000000000000000000000000000004").value(); - txn.data += evmc::from_hex("7265633100000000000000000000000000000000000000000000000000000000").value(); + txn.data += evmc::from_hex(int_str32(64)).value(); //offset of param1 + txn.data += evmc::from_hex(int_str32(3)).value(); //param2 + txn.data += evmc::from_hex(int_str32(4)).value(); //param1 size + txn.data += evmc::from_hex(data_str32(str_to_hex("rec1"))).value(); //param1 data evm1.sign(txn); + auto res = pushtx(txn); BOOST_CHECK(res->action_traces.size() == 4); for(int i=0; i<3; ++i) { - auto out = fc::raw::unpack(res->action_traces[1+i].return_value); + auto msgout = fc::raw::unpack(res->action_traces[1+i].return_value); + auto out = std::get(msgout); + BOOST_CHECK(out.receiver == "rec1"_n); BOOST_CHECK(out.sender == to_bytes(contract_addr)); BOOST_CHECK(out.timestamp.time_since_epoch() == control->pending_block_time().time_since_epoch()); diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi index 95c4f868..d3c52c8b 100644 --- a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi @@ -1,10 +1,15 @@ { "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", "version": "eosio::abi/1.2", - "types": [], + "types": [ + { + "new_type_name": "bridge_message", + "type": "variant_bridge_message_v0" + } + ], "structs": [ { - "name": "onbridgemsg", + "name": "bridge_message_v0", "base": "", "fields": [ { @@ -28,6 +33,16 @@ "type": "bytes" } ] + }, + { + "name": "onbridgemsg", + "base": "", + "fields": [ + { + "name": "msg", + "type": "bridge_message" + } + ] } ], "actions": [ @@ -39,6 +54,11 @@ ], "tables": [], "ricardian_clauses": [], - "variants": [], + "variants": [ + { + "name": "variant_bridge_message_v0", + "types": ["bridge_message_v0"] + } + ], "action_results": [] } \ No newline at end of file diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp index 1c563064..92854950 100644 --- a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp @@ -5,26 +5,7 @@ __attribute__((eosio_wasm_import)) void set_action_return_value(void*, size_t); } -struct msgin { - name receiver; - bytes sender; - time_point timestamp; - bytes value; - bytes data; - - EOSLIB_SERIALIZE(msgin, (receiver)(sender)(timestamp)(value)(data)); -}; - -void evm_bridge_receiver::onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data) { - - msgin output { - .receiver = receiver, - .sender = sender, - .timestamp = timestamp, - .value = value, - .data = data - }; - - auto output_bin = eosio::pack(output); +void evm_bridge_receiver::onbridgemsg(const bridge_message& msg) { + auto output_bin = eosio::pack(msg); set_action_return_value(output_bin.data(), output_bin.size()); } diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp index eb0f87c7..af914e11 100644 --- a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp @@ -1,11 +1,25 @@ #include +#include #include using namespace eosio; typedef std::vector bytes; + +struct bridge_message_v0 { + name receiver; + bytes sender; + time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); +}; + +using bridge_message = std::variant; + CONTRACT evm_bridge_receiver : public contract { public: using contract::contract; - [[eosio::action]] void onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data); + [[eosio::action]] void onbridgemsg(const bridge_message& msg); }; \ No newline at end of file diff --git a/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm index 48733b0a9b5759ec7b1cd840cfdaef522d8ae810..c112091faaf80fe80b04d17c96a198119489a2e8 100755 GIT binary patch literal 3685 zcmb7HOK%(36+ZVdLs~;xW5=}>yDjcawV(kNFwm41i~`{moZ4yA)(Ntw$KurHP$Fq^ ztTbJ4)WC}@yhwKW0Rfyvofz$=$x8kO4Uhy#7HtBgfPn%*_E}W<&K;7Xfe2O!NcVN_ zd4A`dJCuyK1rdoW$FCDzmqkI>RZ$e8xK8-PdisBY^#tn)&LF0hci!}myj zDC;Wi@0=N@c{|z6yMumvl;(Fv{q|0>btfgkr(SYnFv^K|UP}k#?x3BF$0@elvA>;e zZw_B4$>-seODD;b_PG1!l$6^ZrAcQdfzL?uwqdz9wg#KM&09&g-ySBTWIN5%(HPUs zUfL-on43={bKFidx8Z0o9E{TrPdaxt!HPVm_~xC_DDCI%yt|!}&s(RME$h3JE>JsG z)#0ey&&TbpQh^hTR<}0?os=r-nUhpKwnWF4mVzZANYv_DYflV?5T3%?KdlO3clRhL z$liZ6A1cR$mkftnuha3RFVCFR6rKnsWGMVmST|(E%eu9(`|$HmKmX6a9=}@{i8XJB z=R89jyZqbx=$+5#72UFK{lzDL|K^QvDXay7w{SjX1oHR4`}cpRjBw*GySocF0!!iP zU?fx_A*D{1ma%22A@5;r!<|@Uv20>h?%r+1%1~yPGdsK;Q&Zk;;i9x*Zs@ms9dnT} zy%Cs1BEYd|ku9$9seNmYLKzqpND7)l6hDKEUW}8btl=``)*6xy*zZEFabhezkMUkB z_=S)KR1=2IMMAa|_=n3e)rF-9j2iN%7}(skM)Dti+spaw#cyQZ&col{SA7hPOuVo zYFqrej!-pZHP*1dmcf>|(ld(5D@OMo9Cn{C8Fy**|GTv2E?qdvrE4$D6jdCHNaIh{ z1pEVks9v~Y#4qJ~1ei6Xjx-M-2#hqE-64%?N?i^FFL?JOMB8}m1rJ`Z6=1;b)9ukE zT48U1r={Pxu>XU@ZY1*sLj{nmWLAXgEVBbnVrGpy-Zl6SWYtJRvB3EquEW))4B78j z>LLiAB~}Wc1JG6+FF*jStjSg>ct;8kXm$~S3DsCSgJRV);>9{^t}I<5gxT%Sq$6HM z7K=L7Bm@KG96Q~MJp=W8Tf78-?c-O8lw%FA04?@^*it~g@gZO2$PNJG#dC4#Vn~W4 zyI6%MBj6s^W)Ucjm&I_!fiCO>#@SSd0N&#$lv)S~>P74yh`0(_0GE;MJr^R5pz$4I z%MgK}DS<=88;*G?!q= zk`A@`_)%?Mb%LL6bLqUP%|M2ARf8|D9ZBo@QM9ffRqG2*aP0SD^}~_!ku(f(hcDy} zqlSsrKo3jH8S3K;j*hIoOfkw>BSms0Aern z&T_T`yO+xW;OG4;_L}nQ1_8PXjzTL?D3Z1@pM~>Tbv{GeST~AkO*yQT1M4OgN)mI1 zMredzyNU=9UXD1MeolnK3&~S#Uq(IgI749azPKIB{J~uZP}n4Hb7D(d$s*K~hP;ZK zsMoGwD!WG+`1tq3A4A=bc@dsrs%vNz7+yu$3a2TKE#~B`$37}W8R2!;$I3bS%be!I z0u7qge1FyLGk5g8`EG5xix_dtzK~_R-$TUIY8fEO(os4V9DLwvbO`tp1nkeo6K+K- z;7zn>cRl>cSttt}&lP7(^CO@{Id=~@thA4lbau3w9USp}^osIM?TG`U&Z{cNU$BiK zS6a-@D|1nyXYyX7DNQ+ht|`}$T412g%TstiU;!~$tnmU3!F4jIFHe=L(lmGL0tjJ+ zv$`RFh42cdU&ruVsS$z<@Vxz~d_{4vx#I~uiQoZLIk<$YwnWu-&oTSh;fy!A|DbIw zR4@(Kb4z#C4_yuJL{ZPepTtS18yD|AqESM^|`SlU+3kD>jx{1 z$L9w^xmRys{+M*P(vHaoCfUL#Pm-r*nA}XqH2c|ydlbAjTk%)YILCL>wn_3l-5%!U z<<~}CcXOPZ{8nnxeuwZKLx+*`hZp25Po%BxxlLGyoA^ILm C*lq*> literal 3112 zcma)8ONbmr82;AWRk!oLUl0d>cG?n{9Vn-R|9;$xJ5_ z4?C;m;30zE;=!BXMJ3?D$7uzR9)b_hgAz~>1o7^{`2AJ;XfZ)|nXan8{>S$}yD6Kj z3nCJa-L*utB&SnaQq$>FOqU2hoOhm^IB(*-iTlk>JO$qfunQc*=_Z75khqec$&Si5 zE(O^O!*NB#`*uE@tPTg+WRim&v6dB>8Z3l2?Olxmq1=490nNalA3Ol&xLN zJ!`ykvyxTWV6ysZPKt%%ah|R0O7R_OCL7lE!rE}TTz)xQ-589rakidU`FMiua+$Aq zrRJsoVsqM#u(r{7I2um!72d2|T!tzN*i?J@;&_~IRD)`DJ*SXKhgmJ-y_DCuovP+& zyt+|M25TO`fqF~^%fpqN8tOny&6yU>v|3TCEp$g~9f*+-B2a=vctlNw-P)#TO7`t9 z_Ljzs2(r;=?K0if`s7foDLxQwlA-wSxMRqOXLWyZ>-zWKe*eplzrHs$5@#mI#{xr( zTl}~E`L*xpyzX0f{^6UCe}DZCirY~X)b4i_f&HUDfBJ1l5hPyQ+Nw!Jmf|DPSg5Il zl@3{&1<6uZUd7qQmr_xrvWJtWUg@XGP+?~aJGzikPhROmP};aM^f-%SFAC;20-Hoc zxE6h~(?|K%zO_xUh>VIPMLi*=_rgY*;-)9t5Qg1$SJJ-vE!Z{AjHO4gUhPK@3ONNd zVd!`wWM4slJeyKSSW2L%D?jL`@=QkGpSRUrZNZ?@bdqSx!Q|)my5)^X8Oh$Vg4Wo;+ z1zE=NRUL;s4eJs=1!Vyvzyk<0BhZXM!JY;J)X!-K)01a;fySPEqArvMdkAEr7=bjw zE&}FH6=WF!q7eI}aI9K|QH5!ircoMM+PFwGsLk@jM4&9-6BEQ@w7PczKWHVfdKGa(9>3#Tiai3Xj+{XDzhlh66(88F|V0?)Z3z6U7= zYpR`~(Phj*^dt_S6Kq5CA?_d0Py4Vs`q>(Np+?JmU;aiY~8hRA3K&Z7`8R3@5@T5*<3(+J;W3=#thu7o|=#Ns>w12?|lGzi2W z@gfe^HG#PUEgTi69%^mlF*nsd<*DX~9;dKP?w*|(HBs&yp*etL1=u!=_(b}DG^yKY zVg*Rx2`u+xiQZKh->kz?qhuI-ja5>XDbKCnoGAGHviM?K&i z2gf*pu4UbVj58fRoHdH4;R1Znw&A9|{n>f0P@wF&LOmhO&Ur||P40Z;u_sS@;Vw>? zW_Wgs`1mnTG|&hY(9ea=VZFU_Q&Y~Yb^UW*RlKJ^_WId&)88S%-nKK$BXq&xzyq2U z#s@0xsK-Knkh%z9#QQ#k&A3KCT%&hu^!pth3eoO}>Mf!0{k5s_(n2RCauE{~m8}(W zgvfFgASrk^zWK#Js^|ft1h0H9Tl5f@kTQY2MHdkLTjD}0s}HYmwrzu_T$rFQ?3TEI zDd&>*4ANO|W10Xj2AF(bqlzu14{)N2NKN4jRU-n--em0f+#CHs^_rT04)? z(Wt!BRP^Hs-el&@FAO3xfx|I;_#l^1?xyy_$;m8o9NO}?>0cVgvFY1pW7;yae$L!_@6qQ3#l(G?5; From ccaa5d7cb07b191013fd61dbc6f2e926f63c7991 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Mon, 21 Aug 2023 15:21:02 -0300 Subject: [PATCH 26/26] Add force_atomic parameter to method bridgeMsgV0 --- include/evm_runtime/bridge.hpp | 31 ++++--- include/evm_runtime/evm_contract.hpp | 1 + include/evm_runtime/print_tracer.hpp | 18 ++-- include/evm_runtime/tables.hpp | 17 +++- silkworm | 2 +- src/actions.cpp | 94 ++++++++++++-------- tests/basic_evm_tester.cpp | 34 ++++++++ tests/basic_evm_tester.hpp | 13 ++- tests/bridge_message_tests.cpp | 126 ++++++++++++++------------- 9 files changed, 209 insertions(+), 127 deletions(-) diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp index 17fb3e42..d931cecd 100644 --- a/include/evm_runtime/bridge.hpp +++ b/include/evm_runtime/bridge.hpp @@ -5,9 +5,10 @@ namespace evm_runtime { namespace bridge { struct message_v0 { - static constexpr uint32_t id = 0x24578ea5; //sha3('bridgeMsgV0(string,bytes)')[:4] + static constexpr uint32_t id = 0xf781185b; //sha3('bridgeMsgV0(string,bool,bytes)')[:4] string account; + bool force_atomic; //currently only atomic is supported bytes data; name get_account_as_name() const { @@ -24,18 +25,20 @@ struct message_v0 { using message = std::variant; message_v0 decode_message_v0(eosio::datastream& ds) { - // offset_p1 (32) + offset_p2 (32) + // offset_p1 (32) + p2_value (32) + offset_p3 (32) // p1_len (32) + p1_data ((p1_len+31)/32*32) - // p2_len (32) + p1_data ((p2_len+31)/32*32) - uint256 offset_p1, offset_p2; - uint32_t p1_len, p2_len; + // p3_len (32) + p3_data ((p2_len+31)/32*32) + uint256 offset_p1, value_p2, offset_p3; ds >> offset_p1; - eosio::check(offset_p1 == 0x40, "invalid p1 offset"); - ds >> offset_p2; - eosio::check(offset_p2 == 0x80, "invalid p2 offset"); + eosio::check(offset_p1 == 0x60, "invalid p1 offset"); + ds >> value_p2; + eosio::check(value_p2 <= 1, "invalid p2 value"); + ds >> offset_p3; + eosio::check(offset_p3 == 0xA0, "invalid p3 offset"); message_v0 res; + res.force_atomic = value_p2 ? true : false; auto get_length=[&]() -> uint32_t { uint256 len; @@ -44,17 +47,17 @@ message_v0 decode_message_v0(eosio::datastream& ds) { return static_cast(len); }; - p1_len = get_length(); + uint32_t p1_len = get_length(); auto p1_len_32 = (p1_len+31)/32*32; res.account.resize(p1_len_32); ds.read(res.account.data(), p1_len_32); res.account.resize(p1_len); - p2_len = get_length(); - auto p2_len_32 = (p2_len+31)/32*32; - res.data.resize(p2_len_32); - ds.read(res.data.data(), p2_len_32); - res.data.resize(p2_len); + uint32_t p3_len = get_length(); + auto p3_len_32 = (p3_len+31)/32*32; + res.data.resize(p3_len_32); + ds.read(res.data.data(), p3_len_32); + res.data.resize(p3_len); return res; } diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 79d94731..3efe729a 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -138,6 +138,7 @@ class [[eosio::contract]] evm_contract : public contract } silkworm::Receipt execute_tx(eosio::name miner, silkworm::Block& block, silkworm::Transaction& tx, silkworm::ExecutionProcessor& ep, bool enforce_chain_id); + void process_filtered_messages(const std::vector& filtered_messages); uint64_t get_and_increment_nonce(const name owner); diff --git a/include/evm_runtime/print_tracer.hpp b/include/evm_runtime/print_tracer.hpp index ef92a023..6f7594ec 100644 --- a/include/evm_runtime/print_tracer.hpp +++ b/include/evm_runtime/print_tracer.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include namespace evm_runtime { -struct print_tracer : EvmTracer { +struct print_tracer : silkworm::EvmTracer { const char* const* opcode_names_ = nullptr; @@ -19,27 +20,28 @@ struct print_tracer : EvmTracer { } void on_instruction_start(uint32_t pc, const intx::uint256* stack_top, int stack_height, - const evmone::ExecutionState& state, - const IntraBlockState& intra_block_state) noexcept override { - const auto opcode = state.code[pc]; + int64_t gas, const evmone::ExecutionState& state, + const silkworm::IntraBlockState& intra_block_state) override { + + const auto opcode = state.original_code[pc]; auto opcode_name = get_opcode_name(opcode_names_, opcode); eosio::print(opcode_name.c_str(), "\n"); } - void on_execution_end(const evmc_result& result, const IntraBlockState& intra_block_state) noexcept override { + void on_execution_end(const evmc_result& result, const silkworm::IntraBlockState& intra_block_state) noexcept override { eosio::print("TRACE: end\n"); } - void on_creation_completed(const evmc_result& result, const IntraBlockState& intra_block_state) noexcept override { + void on_creation_completed(const evmc_result& result, const silkworm::IntraBlockState& intra_block_state) noexcept override { } void on_precompiled_run(const evmc_result& result, int64_t gas, - const IntraBlockState& intra_block_state) noexcept override { + const silkworm::IntraBlockState& intra_block_state) noexcept override { } - void on_reward_granted(const CallResult& result, const IntraBlockState& intra_block_state) noexcept override { + void on_reward_granted(const silkworm::CallResult& result, const silkworm::IntraBlockState& intra_block_state) noexcept override { } diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index 174f9bb4..c44b6768 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -183,14 +183,23 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] allowed_egress_accou typedef eosio::multi_index<"egresslist"_n, allowed_egress_account> egresslist; struct [[eosio::table]] [[eosio::contract("evm_contract")]] message_receiver { - name account; - asset min_fee; + + enum flag : uint32_t { + FORCE_ATOMIC = 0x1 + }; + + name account; + asset min_fee; + uint32_t flags; uint64_t primary_key() const { return account.value; } + bool has_flag(flag f) const { + return (flags & f) != 0; + } - EOSLIB_SERIALIZE(message_receiver, (account)(min_fee)); + EOSLIB_SERIALIZE(message_receiver, (account)(min_fee)(flags)); }; typedef eosio::multi_index<"msgreceiver"_n, message_receiver> message_receiver_table; -} //namespace evm_runtime \ No newline at end of file +} //namespace evm_runtime diff --git a/silkworm b/silkworm index d661be9a..e33efe3f 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit d661be9a624505fb769e9c964ab5ba995e56c94a +Subproject commit e33efe3fd925b9f9eec8eef8b69a3cf523c7626a diff --git a/src/actions.cpp b/src/actions.cpp index a22d255d..22d5fba3 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -385,6 +385,61 @@ void evm_contract::exec(const exec_input& input, const std::optional& filtered_messages ) { + + intx::uint256 accumulated_value; + for(const auto& rawmsg : filtered_messages) { + auto msg = bridge::decode_message(ByteView{rawmsg.data}); + eosio::check(msg.has_value(), "unable to decode bridge message"); + + auto& msg_v0 = std::get(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"); + + message_receiver_table message_receivers(get_self(), get_self().value); + auto it = message_receivers.find(receiver.value); + eosio::check(it != message_receivers.end(), "receiver not registered"); + + eosio::check(msg_v0.force_atomic == false || it->has_flag(message_receiver::FORCE_ATOMIC), "unable to process message"); + + intx::uint256 min_fee((uint64_t)it->min_fee.amount); + min_fee *= minimum_natively_representable; + + auto value = intx::be::unsafe::load(rawmsg.value.bytes); + eosio::check(value >= min_fee, "min_fee not covered"); + + balances balance_table(get_self(), get_self().value); + const balance& receiver_account = balance_table.get(msg_v0.get_account_as_name().value, "receiver account is not open"); + + action(std::vector{}, msg_v0.get_account_as_name(), "onbridgemsg"_n, + bridge_message{ bridge_message_v0 { + .receiver = msg_v0.get_account_as_name(), + .sender = to_bytes(rawmsg.sender), + .timestamp = eosio::current_time_point(), + .value = to_bytes(rawmsg.value), + .data = msg_v0.data + } } + ).send(); + + balance_table.modify(receiver_account, eosio::same_payer, [&](balance& row) { + row.balance += value; + }); + + accumulated_value += value; + } + + if(accumulated_value > 0) { + balances balance_table(get_self(), get_self().value); + const balance& self_balance = balance_table.get(get_self().value); + balance_table.modify(self_balance, eosio::same_payer, [&](balance& row) { + row.balance -= accumulated_value; + }); + } + +} + void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { LOGTIME("EVM START"); @@ -425,43 +480,7 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { auto receipt = execute_tx(miner, block, tx, ep, true); - for(const auto& rawmsg : ep.state().filtered_messages()) { - - auto msg = bridge::decode_message(ByteView{rawmsg.data}); - eosio::check(msg.has_value(), "unable to decode bridge message"); - - auto& msg_v0 = std::get(msg.value()); - - const auto& receiver = msg_v0.get_account_as_name(); - eosio::check(eosio::is_account(receiver), "receiver is not account"); - - message_receiver_table message_receivers(get_self(), get_self().value); - auto it = message_receivers.find(receiver.value); - eosio::check(it != message_receivers.end(), "receiver not registered"); - - intx::uint256 min_fee((uint64_t)it->min_fee.amount); - min_fee *= minimum_natively_representable; - - auto value = intx::be::unsafe::load(rawmsg.value.bytes); - eosio::check(value >= min_fee, "min_fee not covered"); - - balances balance_table(get_self(), get_self().value); - const balance& receiver_account = balance_table.get(msg_v0.get_account_as_name().value, "receiver account is not open"); - - action(std::vector{}, msg_v0.get_account_as_name(), "onbridgemsg"_n, - bridge_message{ bridge_message_v0 { - .receiver = msg_v0.get_account_as_name(), - .sender = to_bytes(rawmsg.sender), - .timestamp = eosio::current_time_point(), - .value = to_bytes(rawmsg.value), - .data = msg_v0.data - } } - ).send(); - - balance_table.modify(receiver_account, eosio::same_payer, [&](balance& row) { - row.balance += value; - }); - } + process_filtered_messages(ep.state().filtered_messages()); engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); @@ -621,6 +640,7 @@ void evm_contract::bridgereg(eosio::name receiver, const eosio::asset& min_fee) auto update_row = [&](auto& row) { row.account = receiver; row.min_fee = min_fee; + row.flags = message_receiver::flag::FORCE_ATOMIC; }; message_receiver_table message_receivers(get_self(), get_self().value); diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index 2e31361c..0fd73d47 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -545,4 +545,38 @@ bool basic_evm_tester::scan_account_storage(uint64_t account_id, std::function visitor) const { + static constexpr eosio::chain::name balances_table_name = "balances"_n; + scan_table( + balances_table_name, evm_account_name, [this, &visitor](vault_balance_row&& row) { + return visitor(row); + } + ); +} + +asset basic_evm_tester::get_eos_balance( const account_name& act ) { + vector data = get_row_by_account( "eosio.token"_n, act, "accounts"_n, name(native_symbol.to_symbol_code().value) ); + return data.empty() ? asset(0, native_symbol) : fc::raw::unpack(data); +} + +void basic_evm_tester::check_balances() { + intx::uint256 total_in_evm_accounts; + scan_accounts([&](account_object&& account) -> bool { + total_in_evm_accounts += account.balance; + return false; + }); + + auto in_evm = intx::uint256(inevm()); + BOOST_REQUIRE(total_in_evm_accounts == in_evm); + + intx::uint256 total_in_accounts; + scan_balances([&](vault_balance_row&& row) -> bool { + total_in_accounts += intx::uint256(balance_and_dust{.balance=row.balance, .dust=row.dust}); + return false; + }); + + auto evm_eos_balance = intx::uint256(balance_and_dust{.balance=get_eos_balance(evm_account_name), .dust=0}); + BOOST_REQUIRE(evm_eos_balance == total_in_accounts+total_in_evm_accounts); +} + } // namespace evm_test diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 726c8518..beb53b6d 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -110,8 +110,9 @@ struct exec_output { }; struct message_receiver { - name account; - asset min_fee; + name account; + asset min_fee; + uint32_t flags; }; struct bridge_message_v0 { @@ -138,7 +139,7 @@ FC_REFLECT(evm_test::exec_input, (context)(from)(to)(data)(value)) FC_REFLECT(evm_test::exec_callback, (contract)(action)) FC_REFLECT(evm_test::exec_output, (status)(data)(context)) -FC_REFLECT(evm_test::message_receiver, (account)(min_fee)); +FC_REFLECT(evm_test::message_receiver, (account)(min_fee)(flags)); FC_REFLECT(evm_test::bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); namespace evm_test { @@ -165,6 +166,7 @@ class evm_eoa std::basic_string public_key; }; +struct vault_balance_row; class basic_evm_tester : public testing::validating_tester { public: @@ -237,6 +239,10 @@ class basic_evm_tester : public testing::validating_tester std::optional evm_balance(const evmc::address& address) const; std::optional evm_balance(const evm_eoa& account) const; + asset get_eos_balance( const account_name& act ); + + void check_balances(); + template void scan_table(eosio::chain::name table_name, eosio::chain::name scope_name, Visitor&& visitor) const { @@ -266,6 +272,7 @@ class basic_evm_tester : public testing::validating_tester std::optional scan_for_account_by_address(const evmc::address& address) const; std::optional find_account_by_address(const evmc::address& address) const; bool scan_account_storage(uint64_t account_id, std::function visitor) const; + void scan_balances(std::function visitor) const; }; inline constexpr intx::uint256 operator"" _wei(const char* s) { return intx::from_string(s); } diff --git a/tests/bridge_message_tests.cpp b/tests/bridge_message_tests.cpp index 42d3e774..b2b2d10a 100644 --- a/tests/bridge_message_tests.cpp +++ b/tests/bridge_message_tests.cpp @@ -10,6 +10,9 @@ using eosio::testing::eosio_assert_message_is; using eosio::testing::expect_assert_message; struct bridge_message_tester : basic_evm_tester { + + static constexpr const char* bridgeMsgV0_method_id = "f781185b"; + bridge_message_tester() { create_accounts({"alice"_n}); transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); @@ -17,6 +20,11 @@ struct bridge_message_tester : basic_evm_tester { open("alice"_n); } + ~bridge_message_tester() { + dlog("~bridge_message_tester()"); + check_balances(); + } + std::string int_str32(uint32_t x) { std::stringstream hex_ss; hex_ss << std::hex << x; @@ -47,13 +55,14 @@ struct bridge_message_tester : basic_evm_tester { transaction_trace_ptr send_bridge_message(evm_eoa& eoa, const std::string& receiver, const intx::uint256& value, const std::string& str_data) { silkworm::Bytes data; - data += evmc::from_hex("24578ea5").value(); - data += evmc::from_hex(int_str32(64)).value(); - data += evmc::from_hex(int_str32(128)).value(); - data += evmc::from_hex(int_str32(receiver.length())).value(); - data += evmc::from_hex(data_str32(str_to_hex(receiver))).value(); - data += evmc::from_hex(int_str32(str_data.size()/2)).value(); - data += evmc::from_hex(data_str32(str_data)).value(); + data += evmc::from_hex(bridgeMsgV0_method_id).value(); + data += evmc::from_hex(int_str32(96)).value(); //offset param1 (receiver: string) + data += evmc::from_hex(int_str32(1)).value(); //param2 (true) (force_atomic: bool) + data += evmc::from_hex(int_str32(160)).value(); //offset param3 (data: bytes) + data += evmc::from_hex(int_str32(receiver.length())).value(); //param1 length + data += evmc::from_hex(data_str32(str_to_hex(receiver))).value(); //param1 data + data += evmc::from_hex(int_str32(str_data.size()/2)).value(); //param3 length + data += evmc::from_hex(data_str32(str_data)).value(); //param3 data return send_raw_message(eoa, make_reserved_address(evm_account_name), value, data); } @@ -88,6 +97,7 @@ BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_message_tester) try { auto row = fc::raw::unpack(get_row_by_account( evm_account_name, evm_account_name, "msgreceiver"_n, "rec1"_n)); BOOST_REQUIRE(row.account == "rec1"_n); BOOST_REQUIRE(row.min_fee == make_asset(0)); + BOOST_REQUIRE(row.flags == 0x1); // Register again changing min fee bridgereg("rec1"_n, make_asset(1)); @@ -95,6 +105,7 @@ BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_message_tester) try { row = fc::raw::unpack(get_row_by_account( evm_account_name, evm_account_name, "msgreceiver"_n, "rec1"_n)); BOOST_REQUIRE(row.account == "rec1"_n); BOOST_REQUIRE(row.min_fee == make_asset(1)); + BOOST_REQUIRE(row.flags == 0x1); // Unregister rec1 bridgeunreg("rec1"_n); @@ -116,37 +127,21 @@ BOOST_FIXTURE_TEST_CASE(basic_tests, bridge_message_tester) try { set_abi("rec1"_n, testing::contracts::evm_bridge_receiver_abi().data()); // Register rec1 with 1000.0000 EOS as min_fee - bridgereg("rec1"_n, make_asset(10000000)); + bridgereg("rec1"_n, make_asset(1000'0000)); // Fund evm1 address with 1001 EOS evm_eoa evm1; - const int64_t to_bridge = 10010000; + const int64_t to_bridge = 1001'0000; transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); - // Get `rec1` and contract balance before sending the message - auto pre_evm_balance = inevm(); - auto pre_rec1_balance = vault_balance("rec1"_n); - BOOST_CHECK(pre_rec1_balance.balance == make_asset(0)); // 0.0000 EOS - BOOST_CHECK(pre_rec1_balance.dust == 0); + // Check rec1 balance before sending the message + BOOST_REQUIRE(vault_balance("rec1"_n) == (balance_and_dust{make_asset(0), 0})); // Emit message - auto res = send_bridge_message(evm1, "rec1", 1000*1_ether, "0102030405060708090a"); + auto res = send_bridge_message(evm1, "rec1", 1000_ether, "0102030405060708090a"); - // Get `rec1` and contract balance after sending the message - auto post_evm_balance = inevm(); - auto post_rec1_balance = vault_balance("rec1"_n); - BOOST_CHECK(post_rec1_balance.balance == make_asset(10000000)); //1000.0000 EOS - BOOST_CHECK(post_rec1_balance.dust == 0); - - // The data length of the bridge message transaction is 196 bytes long - // 22 non zero, 174 zero => total gas is: 21000 + 174*4+22*16 - auto total_gas_fee = intx::uint256(suggested_gas_price)*intx::uint256(21000+174*4+22*16); - dlog("totalgas: ${t}", ("t",total_gas_fee)); - - // Check that the EOS `out` from the EVM is equal to the value sent in the bridge transaction (goes to rec1) - // plus the transaction fee (goes to miner) - auto diff = static_cast(pre_evm_balance)-static_cast(post_evm_balance); - BOOST_CHECK_EQUAL(diff, 1000*1_ether + total_gas_fee); + // Check rec1 balance after sending the message + BOOST_REQUIRE(vault_balance("rec1"_n) == (balance_and_dust{make_asset(1000'0000), 0})); // Recover message form the return value of rec1 contract BOOST_CHECK(res->action_traces[1].receiver == "rec1"_n); @@ -156,9 +151,8 @@ BOOST_FIXTURE_TEST_CASE(basic_tests, bridge_message_tester) try { BOOST_CHECK(out.receiver == "rec1"_n); BOOST_CHECK(out.sender == to_bytes(evm1.address)); BOOST_CHECK(out.timestamp.time_since_epoch() == control->pending_block_time().time_since_epoch()); - BOOST_CHECK(out.value == to_bytes(1000*1_ether)); + BOOST_CHECK(out.value == to_bytes(1000_ether)); BOOST_CHECK(out.data == to_bytes(evmc::from_hex("0102030405060708090a").value())); - } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { @@ -178,35 +172,45 @@ BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); evm1.next_nonce--; - // Send message with 4 bytes NOT matching the 'bridgeMsgV0(string,bytes)' signature + // Send message with 4 bytes NOT matching the 'bridgeMsgV0(string,bool,bytes)' signature BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("01020304").value()), eosio_assert_message_exception, eosio_assert_message_is("unable to decode bridge message")); evm1.next_nonce--; - // Send message with 4 bytes matching the 'bridgeMsgV0(string,bytes)' signature - BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("24578ea5").value()), + // Send message with 4 bytes matching the 'bridgeMsgV0(string,bool,bytes)' signature + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex(bridgeMsgV0_method_id).value()), eosio_assert_message_exception, eosio_assert_message_is("datastream attempted to read past the end")); evm1.next_nonce--; // Wrong p1 offset - BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex("24578ea5").value() + + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, evmc::from_hex(bridgeMsgV0_method_id).value() + evmc::from_hex(int_str32(11)).value()), eosio_assert_message_exception, eosio_assert_message_is("invalid p1 offset")); evm1.next_nonce--; - // Wrong p2 offset + // Wrong p2 value BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("24578ea5").value() + - evmc::from_hex(int_str32(64)).value() + + evmc::from_hex(bridgeMsgV0_method_id).value() + + evmc::from_hex(int_str32(96)).value() + evmc::from_hex(int_str32(11)).value()), - eosio_assert_message_exception, eosio_assert_message_is("invalid p2 offset")); + eosio_assert_message_exception, eosio_assert_message_is("invalid p2 value")); + evm1.next_nonce--; + + // Wrong p3 offset + BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, + evmc::from_hex(bridgeMsgV0_method_id).value() + + evmc::from_hex(int_str32(96)).value()+ + evmc::from_hex(int_str32(1)).value()+ + evmc::from_hex(int_str32(11)).value()), + eosio_assert_message_exception, eosio_assert_message_is("invalid p3 offset")); evm1.next_nonce--; // abcd is not an account BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("24578ea5").value() + - evmc::from_hex(int_str32(64)).value() + - evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(bridgeMsgV0_method_id).value() + + evmc::from_hex(int_str32(96)).value() + + evmc::from_hex(int_str32(1)).value() + + evmc::from_hex(int_str32(160)).value() + evmc::from_hex(int_str32(4)).value() + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + evmc::from_hex(int_str32(4)).value() + @@ -219,9 +223,10 @@ BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { // abcd not registered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("24578ea5").value() + - evmc::from_hex(int_str32(64)).value() + - evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(bridgeMsgV0_method_id).value() + + evmc::from_hex(int_str32(96)).value() + + evmc::from_hex(int_str32(1)).value() + + evmc::from_hex(int_str32(160)).value() + evmc::from_hex(int_str32(4)).value() + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + evmc::from_hex(int_str32(4)).value() + @@ -234,9 +239,10 @@ BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { // min fee not covered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, - evmc::from_hex("24578ea5").value() + - evmc::from_hex(int_str32(64)).value() + - evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(bridgeMsgV0_method_id).value() + + evmc::from_hex(int_str32(96)).value() + + evmc::from_hex(int_str32(1)).value() + + evmc::from_hex(int_str32(160)).value() + evmc::from_hex(int_str32(4)).value() + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + evmc::from_hex(int_str32(4)).value() + @@ -249,31 +255,31 @@ BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { // receiver account is not open BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 1e14, - evmc::from_hex("24578ea5").value() + - evmc::from_hex(int_str32(64)).value() + - evmc::from_hex(int_str32(128)).value() + + evmc::from_hex(bridgeMsgV0_method_id).value() + + evmc::from_hex(int_str32(96)).value() + + evmc::from_hex(int_str32(1)).value() + + evmc::from_hex(int_str32(160)).value() + evmc::from_hex(int_str32(4)).value() + evmc::from_hex(data_str32(str_to_hex("abcd"))).value() + evmc::from_hex(int_str32(4)).value() + evmc::from_hex(data_str32(str_to_hex("data"))).value()), eosio_assert_message_exception, eosio_assert_message_is("receiver account is not open")); - } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE(test_send_message_from_solidity, bridge_message_tester) try { // // SPDX-License-Identifier: GPL-3.0 // pragma solidity >=0.7.0 <0.9.0; // contract Emiter { - // function go(string memory destination, uint256 n) public { + // function go(string memory destination, bool force_atomic, uint256 n) public { // address eosevm = 0xBbBBbbBBbBbbbBBBbBbbBBbB56E4000000000000; // for(uint i=0; i go('rec1', 3) auto txn = generate_tx(contract_addr, 0, 500'000); - txn.data = evmc::from_hex("a5cc93e4").value(); - txn.data += evmc::from_hex(int_str32(64)).value(); //offset of param1 - txn.data += evmc::from_hex(int_str32(3)).value(); //param2 + txn.data = evmc::from_hex("e1963a31").value(); + txn.data += evmc::from_hex(int_str32(96)).value(); //offset of param1 + txn.data += evmc::from_hex(int_str32(1)).value(); //param2 + txn.data += evmc::from_hex(int_str32(3)).value(); //param3 txn.data += evmc::from_hex(int_str32(4)).value(); //param1 size txn.data += evmc::from_hex(data_str32(str_to_hex("rec1"))).value(); //param1 data evm1.sign(txn); @@ -316,7 +323,6 @@ BOOST_FIXTURE_TEST_CASE(test_send_message_from_solidity, bridge_message_tester) BOOST_CHECK(out.value == to_bytes(0_ether)); BOOST_CHECK(out.data == to_bytes(evmc::from_hex("00000000000000000000000000000000000000000000000000000000FFFFFF0"+std::to_string(i)).value())); } - } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file