diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 81b47620..e52669fa 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, 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 @@ -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 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/silkworm b/silkworm index 2f000a5c..3f4e257f 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 2f000a5c5ae74da14e97333ef37bae8b059c63ae +Subproject commit 3f4e257f582542e1de45e486af33e0f1d48088a0 diff --git a/src/actions.cpp b/src/actions.cpp index 893aa4ee..116bf5c9 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,32 +220,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 +278,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; @@ -452,7 +461,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 +564,76 @@ bool evm_contract::gc(uint32_t max) { return state.gc(max); } +void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce) { + 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..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..5f7121ca 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -291,6 +291,44 @@ transaction_trace_ptr basic_evm_tester::exec(const exec_input& input, const std: return basic_evm_tester::push_action(evm_account_name, "exec"_n, evm_account_name, bytes{binary_data.begin(), binary_data.end()}); } +void basic_evm_tester::call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor) +{ + 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)); +} + 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..64303da7 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, 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 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