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##*/}" 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##*/}" diff --git a/include/evm_runtime/bridge.hpp b/include/evm_runtime/bridge.hpp new file mode 100644 index 00000000..d931cecd --- /dev/null +++ b/include/evm_runtime/bridge.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include + +namespace evm_runtime { namespace bridge { + +struct message_v0 { + 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 { + if(!account_.has_value()) { + account_ = name{account}; + eosio::check(account_.value().to_string() == account, "invalid account name"); + } + return *account_; + } + + mutable std::optional account_; +}; + +using message = std::variant; + +message_v0 decode_message_v0(eosio::datastream& ds) { + // offset_p1 (32) + p2_value (32) + offset_p3 (32) + // p1_len (32) + p1_data ((p1_len+31)/32*32) + // p3_len (32) + p3_data ((p2_len+31)/32*32) + uint256 offset_p1, value_p2, offset_p3; + + ds >> offset_p1; + 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; + ds >> len; + eosio::check(len < std::numeric_limits::max(), "invalid length"); + return static_cast(len); + }; + + 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); + + 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; +} + +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(message_v0::id)) return decode_message_v0(ds); + return {}; +} + +} //namespace bridge +} //namespace evm_runtime diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 81b47620..3efe729a 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -84,6 +84,12 @@ 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); + + [[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); [[eosio::action]] void @@ -132,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); @@ -140,6 +147,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 bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce); + // to allow sending through a Bytes (basic_string) w/o copying over to a std::vector void pushtx_bytes(eosio::name miner, const std::basic_string& rlptx); using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx_bytes>; 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 205a2d4c..c44b6768 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -182,4 +182,24 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] allowed_egress_accou typedef eosio::multi_index<"egresslist"_n, allowed_egress_account> egresslist; -} //namespace evm_runtime \ No newline at end of file +struct [[eosio::table]] [[eosio::contract("evm_contract")]] message_receiver { + + 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)(flags)); +}; + +typedef eosio::multi_index<"msgreceiver"_n, message_receiver> message_receiver_table; + +} //namespace evm_runtime diff --git a/include/evm_runtime/types.hpp b/include/evm_runtime/types.hpp index aa050640..50a6293c 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,18 @@ namespace evm_runtime { EOSLIB_SERIALIZE(exec_output, (status)(data)(context)); }; + struct bridge_message_v0 { + eosio::name receiver; + bytes sender; + eosio::time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); + }; + + using bridge_message = std::variant; + } //namespace evm_runtime namespace eosio { diff --git a/silkworm b/silkworm index 2f000a5c..e33efe3f 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 2f000a5c5ae74da14e97333ef37bae8b059c63ae +Subproject commit e33efe3fd925b9f9eec8eef8b69a3cf523c7626a 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 893aa4ee..22d5fba3 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -40,6 +41,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,32 +221,40 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction& inevm.emplace(get_self(), get_self().value); }; + 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"); - 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)); + // 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; + 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"); - 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"); } @@ -269,7 +279,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, "tx executed inline by contract must succeed"); if(!ep.state().reserved_objects().empty()) { bool non_open_account_sent = false; @@ -375,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"); @@ -406,8 +471,17 @@ 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"); + // 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 { + 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); + process_filtered_messages(ep.state().filtered_messages()); + engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); LOGTIME("EVM END"); @@ -452,7 +526,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 +629,112 @@ 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; + row.flags = message_receiver::flag::FORCE_ATOMIC; + }; + + 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); +} + +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; + 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}}); + pushtx_act.send(get_self(), rlp); +} + +void evm_contract::call(eosio::name from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) { + assert_unfrozen(); + require_auth(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, 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); + 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)); + + // Prepare 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, v, data, gas_limit, 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..2b1ead9f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,7 +30,9 @@ 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}/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.cpp b/tests/basic_evm_tester.cpp index a9d7ece5..0fd73d47 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,62 @@ basic_evm_tester::generate_tx(const evmc::address& to, const intx::uint256& valu .value = value, }; } + +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()); + 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 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, const evmc::bytes& 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(from.size()); + memcpy(from_bytes.data(), from.data(), from.size()); + + 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)); +} + +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 +354,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) @@ -342,6 +396,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); @@ -487,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 56b79940..beb53b6d 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -109,6 +109,22 @@ struct exec_output { std::optional context; }; +struct message_receiver { + name account; + asset min_fee; + uint32_t flags; +}; + +struct bridge_message_v0 { + name receiver; + bytes sender; + time_point timestamp; + bytes value; + bytes data; +}; + +using bridge_message = std::variant; + } // namespace evm_test @@ -123,6 +139,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)(flags)); +FC_REFLECT(evm_test::bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); + namespace evm_test { class evm_eoa { @@ -147,6 +166,7 @@ class evm_eoa std::basic_string public_key; }; +struct vault_balance_row; class basic_evm_tester : public testing::validating_tester { public: @@ -166,6 +186,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 +219,12 @@ 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; + void call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor); + void admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor); + transaction_trace_ptr 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); @@ -209,10 +234,15 @@ 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; + 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 { @@ -242,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); } @@ -308,4 +339,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_message_tests.cpp b/tests/bridge_message_tests.cpp new file mode 100644 index 00000000..b2b2d10a --- /dev/null +++ b/tests/bridge_message_tests.cpp @@ -0,0 +1,328 @@ +#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_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)); + init(); + 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; + 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_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(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); + } + + 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, "alice"_n); + } +}; + +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"); }); + + // 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_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)); + + 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); + const auto& d = get_row_by_account( evm_account_name, evm_account_name, "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_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 1000.0000 EOS as min_fee + bridgereg("rec1"_n, make_asset(1000'0000)); + + // 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()); + + // 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_ether, "0102030405060708090a"); + + // 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); + 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(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 { + + 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_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{}); + + // 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 '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,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(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 value + 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(11)).value()), + 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(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 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(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 not registered")); + evm1.next_nonce--; + + // 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(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("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(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, 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("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); + + auto res = pushtx(txn); + BOOST_CHECK(res->action_traces.size() == 4); + + for(int i=0; i<3; ++i) { + 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()); + 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 diff --git a/tests/call_tests.cpp b/tests/call_tests.cpp new file mode 100644 index 00000000..085ea3f2 --- /dev/null +++ b/tests/call_tests.cpp @@ -0,0 +1,498 @@ +#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 { + /* + //SPDX-License-Identifier: lgplv3 + pragma solidity ^0.8.0; + + contract Test { + uint256 public count; + address public lastcaller; + + function test(uint256 input) public { + require(input != 0); + + count += input; + lastcaller = msg.sender; + } + + function testpay() payable public { + + } + + function notpayable() public { + + } + + } + */ + // 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 = + "608060405234801561001057600080fd5b5061030f806100206000396000f3fe60806040526004361061004a5760003560e01c806306661abd1461004f57806329e99f071461007a578063a1a7d817146100a3578063d097e7a6146100ad578063d79e1b6a146100d8575b600080fd5b34801561005b57600080fd5b506100646100ef565b60405161007191906101a1565b60405180910390f35b34801561008657600080fd5b506100a1600480360381019061009c91906101ed565b6100f5565b005b6100ab61015e565b005b3480156100b957600080fd5b506100c2610160565b6040516100cf919061025b565b60405180910390f35b3480156100e457600080fd5b506100ed610186565b005b60005481565b6000810361010257600080fd5b8060008082825461011391906102a5565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b565b6000819050919050565b61019b81610188565b82525050565b60006020820190506101b66000830184610192565b92915050565b600080fd5b6101ca81610188565b81146101d557600080fd5b50565b6000813590506101e7816101c1565b92915050565b600060208284031215610203576102026101bc565b5b6000610211848285016101d8565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102458261021a565b9050919050565b6102558161023a565b82525050565b6000602082019050610270600083018461024c565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102b082610188565b91506102bb83610188565b92508282019050808211156102d3576102d2610276565b5b9291505056fea2646970667358221220ed95d8f74110a8eb6307b7ae52b8623fd3e959169b208830a960c99a9ba1dbf564736f6c63430008120033"; + + 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()); + } + + 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 + + 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) { + + 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] + + 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) { + + 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] + + 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) { + + 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] + + 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) { + + 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] + + 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) { + 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 + + 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={}) { + 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_account_name)); + // Fund evm1 address with 100 EOS + 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 == 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_account_name)) == 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, save initial balance value. + transfer_token("alice"_n, evm_account_name, make_asset(1000000), "alice"); + 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); + + 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(); + + call_test(token_addr, 4321, "alice"_n, "alice"_n); + count = get_count(token_addr); + BOOST_REQUIRE(count == 5555); + + 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 { + evm_eoa evm1; + evm_eoa evm2; + + auto total_fund = intx::uint256(vault_balance(evm_account_name)); + + // Fund evm1 address with 100 EOS + 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 == 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_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_account_name), + eosio_assert_message_exception, eosio_assert_message_is("invalid address")); + + // Transfer small amount to create account + transfer_token("alice"_n, evm_account_name, make_asset(100), evm2.address_0x()); + + 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, 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")); + + + // Call and check results + admincall_test(token_addr, 1234, evm2, evm_account_name); + + auto count = get_count(token_addr); + BOOST_REQUIRE(count == 1234); + + 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(); + + admincall_test(token_addr, 4321, evm2, evm_account_name); + count = get_count(token_addr); + BOOST_REQUIRE(count == 5555); + + 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() + + +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, silkworm::Bytes(v), *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, 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 + count = get_count(addr); + BOOST_REQUIRE(count == 2222); +} 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..d3c52c8b --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.abi @@ -0,0 +1,64 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [ + { + "new_type_name": "bridge_message", + "type": "variant_bridge_message_v0" + } + ], + "structs": [ + { + "name": "bridge_message_v0", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "sender", + "type": "bytes" + }, + { + "name": "timestamp", + "type": "time_point" + }, + { + "name": "value", + "type": "bytes" + }, + { + "name": "data", + "type": "bytes" + } + ] + }, + { + "name": "onbridgemsg", + "base": "", + "fields": [ + { + "name": "msg", + "type": "bridge_message" + } + ] + } + ], + "actions": [ + { + "name": "onbridgemsg", + "type": "onbridgemsg", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "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.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..92854950 --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.cpp @@ -0,0 +1,11 @@ +#include "evm_bridge_receiver.hpp" + +extern "C" { +__attribute__((eosio_wasm_import)) +void set_action_return_value(void*, size_t); +} + +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 new file mode 100644 index 00000000..af914e11 --- /dev/null +++ b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.hpp @@ -0,0 +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(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 new file mode 100755 index 00000000..c112091f Binary files /dev/null and b/tests/contracts/evm_bridge_receiver/evm_bridge_receiver.wasm differ 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 { 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