diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index b1e695f3..8cc5921d 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -88,7 +88,7 @@ class [[eosio::contract]] evm_contract : public contract [[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); - [[eosio::action]] void bridgereg(eosio::name receiver, const eosio::asset& min_fee); + [[eosio::action]] void bridgereg(eosio::name receiver, eosio::name handler, const eosio::asset& min_fee); [[eosio::action]] void bridgeunreg(eosio::name receiver); [[eosio::action]] void assertnonce(eosio::name account, uint64_t next_nonce); diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index c44b6768..5f6e99e1 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -189,6 +189,7 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] message_receiver { }; name account; + name handler; asset min_fee; uint32_t flags; @@ -197,7 +198,7 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] message_receiver { return (flags & f) != 0; } - EOSLIB_SERIALIZE(message_receiver, (account)(min_fee)(flags)); + EOSLIB_SERIALIZE(message_receiver, (account)(handler)(min_fee)(flags)); }; typedef eosio::multi_index<"msgreceiver"_n, message_receiver> message_receiver_table; diff --git a/src/actions.cpp b/src/actions.cpp index a0c1cf63..b91cc8c2 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -411,9 +411,9 @@ void evm_contract::process_filtered_messages(const std::vector= 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"); + const balance& receiver_account = balance_table.get(receiver.value, "receiver account is not open"); - action(std::vector{}, msg_v0.get_account_as_name(), "onbridgemsg"_n, + action(std::vector{}, it->handler, "onbridgemsg"_n, bridge_message{ bridge_message_v0 { .receiver = msg_v0.get_account_as_name(), .sender = to_bytes(rawmsg.sender), @@ -699,7 +699,7 @@ void evm_contract::admincall(const bytes& from, const bytes& to, const bytes& va call_(s, to, v, data, gas_limit, nonce); } -void evm_contract::bridgereg(eosio::name receiver, const eosio::asset& min_fee) { +void evm_contract::bridgereg(eosio::name receiver, eosio::name handler, const eosio::asset& min_fee) { assert_unfrozen(); require_auth(receiver); require_auth(get_self()); // to temporarily prevent registration of unauthorized accounts @@ -709,6 +709,7 @@ void evm_contract::bridgereg(eosio::name receiver, const eosio::asset& min_fee) auto update_row = [&](auto& row) { row.account = receiver; + row.handler = handler; row.min_fee = min_fee; row.flags = message_receiver::FORCE_ATOMIC; }; diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index fc1c5d2a..aada23ea 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -333,10 +333,12 @@ void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit)); } -transaction_trace_ptr basic_evm_tester::bridgereg(name receiver, asset min_fee, vector extra_signers) { +transaction_trace_ptr basic_evm_tester::bridgereg(name receiver, name handler, asset min_fee, vector extra_signers) { extra_signers.push_back(receiver); + if (receiver != handler) + extra_signers.push_back(handler); return basic_evm_tester::push_action(evm_account_name, "bridgereg"_n, extra_signers, - mvo()("receiver", receiver)("min_fee", min_fee)); + mvo()("receiver", receiver)("handler", handler)("min_fee", min_fee)); } transaction_trace_ptr basic_evm_tester::bridgeunreg(name receiver) { diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index 9adc416e..69b18dff 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -111,6 +111,7 @@ struct exec_output { struct message_receiver { name account; + name handler; asset min_fee; uint32_t flags; }; @@ -139,7 +140,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)(flags)); +FC_REFLECT(evm_test::message_receiver, (account)(handler)(min_fee)(flags)); FC_REFLECT(evm_test::bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); namespace evm_test { @@ -219,7 +220,7 @@ class basic_evm_tester : public testing::validating_tester silkworm::Transaction generate_tx(const evmc::address& to, const intx::uint256& value, uint64_t gas_limit = 21000) const; - transaction_trace_ptr bridgereg(name receiver, asset min_fee, vector extra_signers={evm_account_name}); + transaction_trace_ptr bridgereg(name receiver, name handler, 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); transaction_trace_ptr assertnonce(name account, uint64_t next_nonce); diff --git a/tests/bridge_message_tests.cpp b/tests/bridge_message_tests.cpp index c1868dd0..bf8b519e 100644 --- a/tests/bridge_message_tests.cpp +++ b/tests/bridge_message_tests.cpp @@ -81,18 +81,18 @@ 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), {}), + BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, "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")), + BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, "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)), + BOOST_REQUIRE_EXCEPTION(bridgereg("rec1"_n, "rec1"_n, make_asset(-1)), eosio_assert_message_exception, eosio_assert_message_is("min_fee cannot be negative")); - bridgereg("rec1"_n, make_asset(0)); + bridgereg("rec1"_n, "rec1"_n, make_asset(0)); 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); @@ -100,7 +100,7 @@ BOOST_FIXTURE_TEST_CASE(bridge_register_test, bridge_message_tester) try { BOOST_REQUIRE(row.flags == 0x1); // Register again changing min fee - bridgereg("rec1"_n, make_asset(1)); + bridgereg("rec1"_n, "rec1"_n, make_asset(1)); row = fc::raw::unpack(get_row_by_account( evm_account_name, evm_account_name, "msgreceiver"_n, "rec1"_n)); BOOST_REQUIRE(row.account == "rec1"_n); @@ -127,7 +127,7 @@ 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(1000'0000)); + bridgereg("rec1"_n, "rec1"_n, make_asset(1000'0000)); // Fund evm1 address with 1001 EOS evm_eoa evm1; @@ -235,7 +235,7 @@ BOOST_FIXTURE_TEST_CASE(test_bridge_errors, bridge_message_tester) try { evm1.next_nonce--; // Register abcd - bridgereg("abcd"_n, make_asset(1)); + bridgereg("abcd"_n, "abcd"_n, make_asset(1)); // min fee not covered BOOST_REQUIRE_EXCEPTION(send_raw_message(evm1, emv_reserved_address, 0, @@ -287,7 +287,7 @@ BOOST_FIXTURE_TEST_CASE(test_send_message_from_solidity, bridge_message_tester) set_abi("rec1"_n, testing::contracts::evm_bridge_receiver_abi().data()); // Register rec1 with 0 EOS as min_fee - bridgereg("rec1"_n, make_asset(0)); + bridgereg("rec1"_n, "rec1"_n, make_asset(0)); // Fund evm1 address with 100 EOS evm_eoa evm1; @@ -325,4 +325,64 @@ BOOST_FIXTURE_TEST_CASE(test_send_message_from_solidity, bridge_message_tester) } } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE(handler_tests, bridge_message_tester) try { + auto emv_reserved_address = make_reserved_address(evm_account_name); + + // Fund evm1 address with 1001 EOS + evm_eoa evm1; + const int64_t to_bridge = 1001'0000; + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + BOOST_REQUIRE_EXCEPTION(send_bridge_message(evm1, "receiver", 0, "0102030405060708090a"), + eosio_assert_message_exception, eosio_assert_message_is("receiver is not account")); + evm1.next_nonce--; + + // Create receiver account + create_accounts({"receiver"_n}); + + // receiver not registered + BOOST_REQUIRE_EXCEPTION(send_bridge_message(evm1, "receiver", 0, "0102030405060708090a"), + eosio_assert_message_exception, eosio_assert_message_is("receiver not registered")); + evm1.next_nonce--; + + // Create handler account + create_accounts({"handler"_n}); + set_code("handler"_n, testing::contracts::evm_bridge_receiver_wasm()); + set_abi("handler"_n, testing::contracts::evm_bridge_receiver_abi().data()); + // Register handler with 1000.0000 EOS as min_fee + bridgereg("receiver"_n, "handler"_n, make_asset(1000'0000)); + + + // Corner case: receiver account is not open + // We can only test in this way because bridgereg will open receiver. + close("receiver"_n); + + BOOST_REQUIRE_EXCEPTION(send_bridge_message(evm1, "receiver", 1000_ether, "0102030405060708090a"), + eosio_assert_message_exception, eosio_assert_message_is("receiver account is not open")); + evm1.next_nonce--; + open("receiver"_n); + + // Check handler balance before sending the message + BOOST_REQUIRE(vault_balance("receiver"_n) == (balance_and_dust{make_asset(0), 0})); + + // Emit message + auto res = send_bridge_message(evm1, "receiver", 1000_ether, "0102030405060708090a"); + + // Check handler balance after sending the message + BOOST_REQUIRE(vault_balance("receiver"_n) == (balance_and_dust{make_asset(1000'0000), 0})); + + // Recover message form the return value of handler contract + // Make sure "handler" received a message sent to "receiver" + BOOST_CHECK(res->action_traces[1].receiver == "handler"_n); + auto msgout = fc::raw::unpack(res->action_traces[1].return_value); + auto out = std::get(msgout); + + BOOST_CHECK(out.receiver == "receiver"_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_ether)); + BOOST_CHECK(out.data == to_bytes(evmc::from_hex("0102030405060708090a").value())); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file