diff --git a/CMakeLists.txt b/CMakeLists.txt index 4aa248c9..b64b69a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,9 @@ option(WITH_LOGTIME option(WITH_LARGE_STACK "Build with 50MB of stack size, needed for unit tests" OFF) +option(WITH_ADMIN_ACTIONS + "Enables admin actions" ON) + ExternalProject_Add( evm_runtime_project SOURCE_DIR ${CMAKE_SOURCE_DIR}/src @@ -25,6 +28,7 @@ ExternalProject_Add( -DWITH_TEST_ACTIONS=${WITH_TEST_ACTIONS} -DWITH_LOGTIME=${WITH_LOGTIME} -DWITH_LARGE_STACK=${WITH_LARGE_STACK} + -DWITH_ADMIN_ACTIONS=${WITH_ADMIN_ACTIONS} UPDATE_COMMAND "" PATCH_COMMAND "" TEST_COMMAND "" diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 8cc5921d..672ef687 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -93,6 +93,15 @@ class [[eosio::contract]] evm_contract : public contract [[eosio::action]] void assertnonce(eosio::name account, uint64_t next_nonce); +#ifdef WITH_ADMIN_ACTIONS + [[eosio::action]] void rmgcstore(uint64_t id); + [[eosio::action]] void setkvstore(uint64_t account_id, const bytes& key, const std::optional& value); + [[eosio::action]] void rmaccount(uint64_t id); + [[eosio::action]] void addevmbal(uint64_t id, const bytes& delta, bool subtract); + [[eosio::action]] void addopenbal(name account, const bytes& delta, bool subtract); + [[eosio::action]] void freezeaccnt(uint64_t id, bool value); +#endif + #ifdef WITH_TEST_ACTIONS [[eosio::action]] void testtx(const std::optional& orlptx, const evm_runtime::test::block_info& bi); [[eosio::action]] void diff --git a/include/evm_runtime/state.hpp b/include/evm_runtime/state.hpp index 328c5b23..9726ff65 100644 --- a/include/evm_runtime/state.hpp +++ b/include/evm_runtime/state.hpp @@ -27,12 +27,13 @@ struct state : State { name _self; name _ram_payer; bool _read_only; + bool _allow_frozen; mutable std::map addr2id; mutable std::map addr2code; mutable db_stats stats; std::optional _config2; - explicit state(name self, name ram_payer, bool read_only=false) : _self(self), _ram_payer(ram_payer), _read_only{read_only}{} + explicit state(name self, name ram_payer, bool read_only=false, bool allow_frozen=true) : _self(self), _ram_payer(ram_payer), _read_only{read_only}, _allow_frozen{allow_frozen}{} virtual ~state() override; uint64_t get_next_account_id(); diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index 68a4c949..019e8fcb 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -4,17 +4,36 @@ #include #include #include +#include + #include #include namespace evm_runtime { using namespace eosio; struct [[eosio::table]] [[eosio::contract("evm_contract")]] account { + enum class flag : uint32_t { + frozen = 0x1 + }; + uint64_t id; bytes eth_address; uint64_t nonce; bytes balance; - std::optional code_id; + std::optional code_id; + binary_extension flags=0; + + void set_flag(flag f) { + flags.value() |= static_cast(f); + } + + void clear_flag(flag f) { + flags.value() &= ~static_cast(f); + } + + inline bool has_flag(flag f)const { + return (flags.value() & static_cast(f) != 0); + } uint64_t primary_key()const { return id; } @@ -28,7 +47,7 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] account { return res; } - EOSLIB_SERIALIZE(account, (id)(eth_address)(nonce)(balance)(code_id)); + EOSLIB_SERIALIZE(account, (id)(eth_address)(nonce)(balance)(code_id)(flags)); }; typedef multi_index< "account"_n, account, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31ed3dc2..e215c18a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,12 +13,18 @@ list(APPEND SOURCES ) if (WITH_TEST_ACTIONS) add_compile_definitions(WITH_TEST_ACTIONS) + list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test_actions.cpp) endif() if (WITH_LOGTIME) add_compile_definitions(WITH_LOGTIME) endif() +if (WITH_ADMIN_ACTIONS) + add_compile_definitions(WITH_ADMIN_ACTIONS) + list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/admin_actions.cpp) +endif() + add_compile_definitions(ANTELOPE) add_compile_definitions(PROJECT_VERSION="0.6.0") @@ -85,5 +91,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=38240) + target_link_options(evm_runtime PUBLIC --stack-size=37600) endif() diff --git a/src/actions.cpp b/src/actions.cpp index df49aa8d..a1afe02c 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -15,11 +15,6 @@ // included here so NDEBUG is defined to disable assert macro #include -#ifdef WITH_TEST_ACTIONS -#include -#include -#endif - #ifdef WITH_LOGTIME #define LOGTIME(MSG) eosio::internal_use_do_not_use::logtime(MSG) #else @@ -460,7 +455,7 @@ void evm_contract::pushtx( eosio::name miner, const bytes& rlptx ) { silkworm::protocol::TrustRuleSet engine{*found_chain_config->second}; - evm_runtime::state state{get_self(), get_self()}; + evm_runtime::state state{get_self(), get_self(), false, false}; silkworm::ExecutionProcessor ep{block, engine, state, *found_chain_config->second}; Transaction tx; @@ -750,371 +745,4 @@ void evm_contract::assertnonce(eosio::name account, uint64_t next_nonce) { } } -#ifdef WITH_TEST_ACTIONS -[[eosio::action]] void evm_contract::testtx( const std::optional& orlptx, const evm_runtime::test::block_info& bi ) { - assert_unfrozen(); - - eosio::require_auth(get_self()); - - Block block; - block.header = bi.get_block_header(); - - evm_runtime::test::engine engine{evm_runtime::test::kTestNetwork}; - evm_runtime::state state{get_self(), get_self()}; - silkworm::ExecutionProcessor ep{block, engine, state, evm_runtime::test::kTestNetwork}; - - if(orlptx) { - Transaction tx; - ByteView bv{(const uint8_t*)orlptx->data(), orlptx->size()}; - eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); - - execute_tx(eosio::name{}, block, tx, ep, false); - } - engine.finalize(ep.state(), ep.evm().block()); - ep.state().write_to_db(ep.evm().block().header.number); -} - -[[eosio::action]] void evm_contract::dumpstorage(const bytes& addy) { - assert_inited(); - - eosio::require_auth(get_self()); - - account_table accounts(_self, _self.value); - auto inx = accounts.get_index<"by.address"_n>(); - auto itr = inx.find(make_key(to_address(addy))); - if(itr == inx.end()) { - eosio::print("no data for: "); - eosio::printhex(addy.data(), addy.size()); - eosio::print("\n"); - return; - } - - eosio::print("storage: "); - eosio::printhex(addy.data(), addy.size()); - - uint64_t cnt=0; - storage_table db(_self, itr->id); - auto sitr = db.begin(); - while(sitr != db.end()) { - eosio::print("\n"); - eosio::printhex(sitr->key.data(), sitr->key.size()); - eosio::print(":"); - eosio::printhex(sitr->value.data(), sitr->value.size()); - eosio::print("\n"); - ++sitr; - ++cnt; - } - - eosio::print(" = ", cnt, "\n"); -} - -[[eosio::action]] void evm_contract::dumpall() { - assert_inited(); - - eosio::require_auth(get_self()); - - auto print_store = [](auto sitr) { - eosio::print(" "); - eosio::printhex(sitr->key.data(), sitr->key.size()); - eosio::print(":"); - eosio::printhex(sitr->value.data(), sitr->value.size()); - eosio::print("\n"); - }; - - account_table accounts(_self, _self.value); - auto itr = accounts.begin(); - eosio::print("DUMPALL start\n"); - while( itr != accounts.end() ) { - eosio::print(" account:"); - eosio::printhex(itr->eth_address.data(), itr->eth_address.size()); - eosio::print("\n"); - storage_table db(_self, itr->id); - auto sitr = db.begin(); - while( sitr != db.end() ) { - print_store( sitr ); - sitr++; - } - - itr++; - } - eosio::print(" gc:"); - gc_store_table gc(_self, _self.value); - auto i = gc.begin(); - while( i != gc.end() ) { - eosio::print(" storage_id:"); - eosio::print(i->storage_id); - eosio::print("\n"); - storage_table db(_self, i->storage_id); - auto sitr = db.begin(); - while( sitr != db.end() ) { - print_store( sitr ); - ++sitr; - } - - ++i; - } - - eosio::print("DUMPALL end\n"); -} - - -[[eosio::action]] void evm_contract::clearall() { - assert_unfrozen(); - - eosio::require_auth(get_self()); - - account_table accounts(_self, _self.value); - auto itr = accounts.begin(); - eosio::print("CLEAR start\n"); - while( itr != accounts.end() ) { - eosio::print(" account:"); - eosio::printhex(itr->eth_address.data(), itr->eth_address.size()); - eosio::print("\n"); - storage_table db(_self, itr->id); - auto sitr = db.begin(); - while( sitr != db.end() ) { - eosio::print(" "); - eosio::printhex(sitr->key.data(), sitr->key.size()); - eosio::print(":"); - eosio::printhex(sitr->value.data(), sitr->value.size()); - eosio::print("\n"); - sitr = db.erase(sitr); - } - - auto db_size = std::distance(db.cbegin(), db.cend()); - eosio::print("db size:", uint64_t(db_size), "\n"); - itr = accounts.erase(itr); - } - - account_code_table codes(_self, _self.value); - auto itrc = codes.begin(); - while(itrc != codes.end()) { - itrc = codes.erase(itrc); - } - - gc(std::numeric_limits::max()); - - auto account_size = std::distance(accounts.cbegin(), accounts.cend()); - eosio::print("accounts size:", uint64_t(account_size), "\n"); - - eosio::print("CLEAR end\n"); -} - -[[eosio::action]] void evm_contract::updatecode( const bytes& address, uint64_t incarnation, const bytes& code_hash, const bytes& code) { - assert_unfrozen(); - - eosio::require_auth(get_self()); - - evm_runtime::state state{get_self(), get_self()}; - auto bvcode = ByteView{(const uint8_t *)code.data(), code.size()}; - state.update_account_code(to_address(address), incarnation, to_bytes32(code_hash), bvcode); -} - -[[eosio::action]] void evm_contract::updatestore(const bytes& address, uint64_t incarnation, const bytes& location, const bytes& initial, const bytes& current) { - assert_unfrozen(); - - eosio::require_auth(get_self()); - - evm_runtime::state state{get_self(), get_self()}; - eosio::print("updatestore: "); - eosio::printhex(address.data(), address.size()); - eosio::print("\n "); - eosio::printhex(location.data(), location.size()); - eosio::print(":"); - eosio::printhex(current.data(), current.size()); - eosio::print("\n"); - - state.update_storage(to_address(address), incarnation, to_bytes32(location), to_bytes32(initial), to_bytes32(current)); -} - -[[eosio::action]] void evm_contract::updateaccnt(const bytes& address, const bytes& initial, const bytes& current) { - assert_unfrozen(); - - eosio::require_auth(get_self()); - - evm_runtime::state state{get_self(), get_self()}; - auto maybe_account = [](const bytes& data) -> std::optional { - std::optional res{}; - if(data.size()) { - Account tmp; - ByteView bv{(const uint8_t *)data.data(), data.size()}; - auto dec_res = Account::from_encoded_storage(bv); - eosio::check(!!dec_res, "unable to decode account"); - res = *dec_res; - } - return res; - }; - - auto oinitial = maybe_account(initial); - auto ocurrent = maybe_account(current); - - state.update_account(to_address(address), oinitial, ocurrent); -} - -[[eosio::action]] void evm_contract::setbal(const bytes& addy, const bytes& bal) { - assert_unfrozen(); - - eosio::require_auth(get_self()); - - account_table accounts(_self, _self.value); - auto inx = accounts.get_index<"by.address"_n>(); - auto itr = inx.find(make_key(addy)); - - if(itr == inx.end()) { - accounts.emplace(get_self(), [&](auto& row){ - row.id = accounts.available_primary_key();; - row.code_id = std::nullopt; - row.eth_address = addy; - row.balance = bal; - }); - } else { - accounts.modify(*itr, eosio::same_payer, [&](auto& row){ - row.balance = bal; - }); - } -} - -[[eosio::action]] void evm_contract::testbaldust(const name test) { - if(test == "basic"_n) { - balance_with_dust b; - // ↱minimum EOS - // .123456789abcdefghi EEOS - b += 200000000_u256; //adds to dust only - b += 3000_u256; //adds to dust only - b += 100000000000000_u256; //adds strictly to balance - b += 200000000007000_u256; //adds to both balance and dust - b += 60000000000000_u256; //adds to dust only - b += 55000000000000_u256; //adds to dust only but dust overflows +1 to balance - - //expect: 415000200010000; .0004 EOS, 15000200010000 dust - check(b.balance.amount == 4, ""); - check(b.dust == 15000200010000, ""); - - // ↱minimum EOS - // .123456789abcdefghi EEOS - b -= 45_u256; //substracts from dust only - b -= 100000000000000_u256; //subtracts strictly from balance - b -= 120000000000000_u256; //subtracts from both dust and balance, causes underflow on dust thus -1 balance - - //expect: 195000200009955; .0001 EOS, 95000200009955 dust - check(b.balance.amount == 1, ""); - check(b.dust == 95000200009955, ""); - } - else if(test == "underflow1"_n) { - balance_with_dust b; - // ↱minimum EOS - // .123456789abcdefghi EEOS - b -= 45_u256; - //should fail with underflow on dust causing an underflow of balance - } - else if(test == "underflow2"_n) { - balance_with_dust b; - // ↱minimum EOS - // .123456789abcdefghi EEOS - b -= 100000000000000_u256; - //should fail with underflow on balance - } - else if(test == "underflow3"_n) { - balance_with_dust b; - // ↱minimum EOS - // .123456789abcdefghi EEOS - b += 200000000000000_u256; - b -= 300000000000000_u256; - //should fail with underflow on balance - } - else if(test == "underflow4"_n) { - balance_with_dust b; - // ↱minimum EOS - // .123456789abcdefghi EEOS - b += 50000_u256; - b -= 500000000_u256; - //should fail with underflow on dust causing an underflow of balance - } - else if(test == "underflow5"_n) { - balance_with_dust b; - // do a decrement that would overflow an int64_t but not uint64_t (for balance) - // ↱int64t max ↱minimum EOS - // 9223372036854775807 (2^63)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 50000_u256; - b -= 100000000000000000000000000000000_u256; - //should fail with underflow - } - else if(test == "overflow1"_n) { - balance_with_dust b; - // increment a value that would overflow a int64_t, but not uint64_t - // ↱int64t max ↱minimum EOS - // 9223372036854775807 (2^63)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 1000000000000000000000000000000000_u256; - //should fail with overflow - } - else if(test == "overflow2"_n) { - balance_with_dust b; - // increment a value that would overflow a max_asset, but not an int64_t - // ↱max_asset max ↱minimum EOS - // 4611686018427387903 (2^62)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 500000000000000000000000000000000_u256; - //should fail with overflow - } - else if(test == "overflow3"_n || test == "overflow4"_n || test == "overflow5"_n || test == "overflowa"_n || test == "overflowb"_n || test == "overflowc"_n) { - balance_with_dust b; - // start with a value that should be the absolute max allowed - // ↱max_asset max ↱minimum EOS - // 4611686018427387903 (2^62)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 461168601842738790399999999999999_u256; - if(test == "overflow4"_n) { - //add 1 to balance, should fail since it rolls balance over max_asset - // ↱minimum EOS - // .123456789abcdefghi EEOS - b += 100000000000000_u256; - //should fail with overflow - } - if(test == "overflow5"_n) { - //add 1 to dust, causing a rollover making balance > max_asset - // ↱minimum EOS - // .123456789abcdefghi EEOS - b += 1_u256; - //should fail with overflow - } - if(test == "overflowa"_n) { - //add something huge - // ↱max_asset max ↱minimum EOS - // 4611686018427387903 (2^62)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 999461168601842738790399999999999999_u256; - //should fail with overflow - } - if(test == "overflowb"_n) { - // add max allowed to balance again; this should be a 2^62-1 + 2^62-1 - // ↱max_asset max ↱minimum EOS - // 4611686018427387903 (2^62)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 461168601842738790300000000000000_u256; - //should fail with overflow - } - if(test == "overflowc"_n) { - // add max allowed to balance again; but also with max dust; should be a 2^62-1 + 2^62-1 + 1 on asset balance - // ↱max_asset max ↱minimum EOS - // 4611686018427387903 (2^62)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 461168601842738790399999999999999_u256; - //should fail with overflow - } - } - if(test == "overflowd"_n) { - balance_with_dust b; - //add something massive - // ↱max_asset max ↱minimum EOS - // 4611686018427387903 (2^62)-1 - // 543210987654321̣123456789abcdefghi EEOS - b += 99999999461168601842738790399999999999999_u256; - //should fail with overflow - } -} - -#endif //WITH_TEST_ACTIONS - } //evm_runtime diff --git a/src/admin_actions.cpp b/src/admin_actions.cpp new file mode 100644 index 00000000..5152e181 --- /dev/null +++ b/src/admin_actions.cpp @@ -0,0 +1,124 @@ +#include +#include +#include + +namespace evm_runtime { +[[eosio::action]] void evm_contract::rmgcstore(uint64_t id) { + eosio::require_auth(get_self()); + gc_store_table gc(get_self(), get_self().value); + auto itr = gc.find(id); + eosio::check(itr != gc.end(), "gc row not found"); + gc.erase(*itr); +} + +[[eosio::action]] void evm_contract::setkvstore(uint64_t account_id, const bytes& key, const std::optional& value) { + eosio::require_auth(get_self()); + eosio::check(key.size() == 32 && (!value.has_value() || value.value().size() == 32), "invalid key/value size"); + + storage_table db(get_self(), account_id); + auto inx = db.get_index<"by.key"_n>(); + auto itr = inx.find(make_key(key)); + + if(value.has_value()) { + if(itr == inx.end()) { + db.emplace(get_self(), [&](auto& row){ + row.id = db.available_primary_key(); + row.key = key; + row.value = value.value(); + }); + } else { + db.modify(*itr, eosio::same_payer, [&](auto& row){ + row.value = value.value(); + }); + } + } else { + eosio::check(itr != inx.end(), "key not found"); + db.erase(*itr); + } +} + +[[eosio::action]] void evm_contract::rmaccount(uint64_t id) { + eosio::require_auth(get_self()); + account_table accounts(get_self(), get_self().value); + auto itr = accounts.find(id); + eosio::check(itr != accounts.end(), "account not found"); + + if (itr->code_id) { + account_code_table codes(get_self(), get_self().value); + const auto& itrc = codes.get(itr->code_id.value(), "code not found"); + if(itrc.ref_count-1) { + codes.modify(itrc, eosio::same_payer, [&](auto& row){ + row.ref_count--; + }); + } else { + codes.erase(itrc); + } + } + + gc_store_table gc(get_self(), get_self().value); + gc.emplace(get_self(), [&](auto& row){ + row.id = gc.available_primary_key(); + row.storage_id = itr->id; + }); + + accounts.erase(*itr); +} + +[[eosio::action]] void evm_contract::addevmbal(uint64_t id, const bytes& delta, bool subtract) { + eosio::require_auth(get_self()); + account_table accounts(get_self(), get_self().value); + auto itr = accounts.find(id); + eosio::check(itr != accounts.end(), "account not found"); + + inevm_singleton inevm(get_self(), get_self().value); + auto d = to_uint256(delta); + + intx::result_with_carry res; + if(subtract) { + inevm.set(inevm.get()-=d, eosio::same_payer); + res = intx::subc(to_uint256(itr->balance), d); + eosio::check(!res.carry, "underflow detected"); + } else { + res = intx::addc(to_uint256(itr->balance), d); + eosio::check(!res.carry, "overflow detected"); + inevm.set(inevm.get()+=d, eosio::same_payer); + } + + accounts.modify(*itr, eosio::same_payer, [&](auto& row){ + row.balance = to_bytes(res.value); + }); +} + +[[eosio::action]] void evm_contract::addopenbal(name account, const bytes& delta, bool subtract) { + eosio::require_auth(get_self()); + balances open_balances(get_self(), get_self().value); + auto itr = open_balances.find(account.value); + eosio::check(itr != open_balances.end(), "account not found"); + + auto d = to_uint256(delta); + + open_balances.modify(*itr, eosio::same_payer, [&](auto& row){ + if(subtract) { + row.balance-=d; + } else { + row.balance+=d; + } + }); +} + +[[eosio::action]] void evm_contract::freezeaccnt(uint64_t id, bool value) { + eosio::require_auth(get_self()); + account_table accounts(get_self(), get_self().value); + auto itr = accounts.find(id); + eosio::check(itr != accounts.end(), "account not found"); + + accounts.modify(*itr, eosio::same_payer, [&](auto& row){ + if(value) { + row.set_flag(account::flag::frozen); + } else { + row.clear_flag(account::flag::frozen); + } + }); +} + +} diff --git a/src/state.cpp b/src/state.cpp index 9013435e..019fdab5 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -16,7 +16,8 @@ std::optional state::read_account(const evmc::address& address) const n if (itr == inx.end()) { return {}; } - + eosio::check(_allow_frozen || !itr->has_flag(account::flag::frozen), "account is frozen"); + addr2id[address] = itr->id; evmc::bytes32 code_hash; @@ -110,6 +111,7 @@ void state::update_account(const evmc::address& address, std::optional row.balance = to_bytes(current->balance); // Codes are not supposed to changed in this call. row.code_id = std::nullopt; + row.flags = 0; }; auto update = [&](auto& row) { diff --git a/src/test_actions.cpp b/src/test_actions.cpp new file mode 100644 index 00000000..6e802f35 --- /dev/null +++ b/src/test_actions.cpp @@ -0,0 +1,374 @@ +#include +#include +#include +#include +#include +#include + +namespace evm_runtime { +using namespace silkworm; + +[[eosio::action]] void evm_contract::testtx( const std::optional& orlptx, const evm_runtime::test::block_info& bi ) { + assert_unfrozen(); + + eosio::require_auth(get_self()); + + Block block; + block.header = bi.get_block_header(); + + evm_runtime::test::engine engine{evm_runtime::test::kTestNetwork}; + evm_runtime::state state{get_self(), get_self()}; + silkworm::ExecutionProcessor ep{block, engine, state, evm_runtime::test::kTestNetwork}; + + if(orlptx) { + Transaction tx; + ByteView bv{(const uint8_t*)orlptx->data(), orlptx->size()}; + eosio::check(rlp::decode(bv,tx) && bv.empty(), "unable to decode transaction"); + + execute_tx(eosio::name{}, block, tx, ep, false); + } + engine.finalize(ep.state(), ep.evm().block()); + ep.state().write_to_db(ep.evm().block().header.number); +} + +[[eosio::action]] void evm_contract::dumpstorage(const bytes& addy) { + assert_inited(); + + eosio::require_auth(get_self()); + + account_table accounts(_self, _self.value); + auto inx = accounts.get_index<"by.address"_n>(); + auto itr = inx.find(make_key(to_address(addy))); + if(itr == inx.end()) { + eosio::print("no data for: "); + eosio::printhex(addy.data(), addy.size()); + eosio::print("\n"); + return; + } + + eosio::print("storage: "); + eosio::printhex(addy.data(), addy.size()); + + uint64_t cnt=0; + storage_table db(_self, itr->id); + auto sitr = db.begin(); + while(sitr != db.end()) { + eosio::print("\n"); + eosio::printhex(sitr->key.data(), sitr->key.size()); + eosio::print(":"); + eosio::printhex(sitr->value.data(), sitr->value.size()); + eosio::print("\n"); + ++sitr; + ++cnt; + } + + eosio::print(" = ", cnt, "\n"); +} + +[[eosio::action]] void evm_contract::dumpall() { + assert_inited(); + + eosio::require_auth(get_self()); + + auto print_store = [](auto sitr) { + eosio::print(" "); + eosio::printhex(sitr->key.data(), sitr->key.size()); + eosio::print(":"); + eosio::printhex(sitr->value.data(), sitr->value.size()); + eosio::print("\n"); + }; + + account_table accounts(_self, _self.value); + auto itr = accounts.begin(); + eosio::print("DUMPALL start\n"); + while( itr != accounts.end() ) { + eosio::print(" account:"); + eosio::printhex(itr->eth_address.data(), itr->eth_address.size()); + eosio::print("\n"); + storage_table db(_self, itr->id); + auto sitr = db.begin(); + while( sitr != db.end() ) { + print_store( sitr ); + sitr++; + } + + itr++; + } + eosio::print(" gc:"); + gc_store_table gc(_self, _self.value); + auto i = gc.begin(); + while( i != gc.end() ) { + eosio::print(" storage_id:"); + eosio::print(i->storage_id); + eosio::print("\n"); + storage_table db(_self, i->storage_id); + auto sitr = db.begin(); + while( sitr != db.end() ) { + print_store( sitr ); + ++sitr; + } + + ++i; + } + + eosio::print("DUMPALL end\n"); +} + +[[eosio::action]] void evm_contract::clearall() { + assert_unfrozen(); + + eosio::require_auth(get_self()); + + account_table accounts(_self, _self.value); + auto itr = accounts.begin(); + eosio::print("CLEAR start\n"); + while( itr != accounts.end() ) { + eosio::print(" account:"); + eosio::printhex(itr->eth_address.data(), itr->eth_address.size()); + eosio::print("\n"); + storage_table db(_self, itr->id); + auto sitr = db.begin(); + while( sitr != db.end() ) { + eosio::print(" "); + eosio::printhex(sitr->key.data(), sitr->key.size()); + eosio::print(":"); + eosio::printhex(sitr->value.data(), sitr->value.size()); + eosio::print("\n"); + sitr = db.erase(sitr); + } + + auto db_size = std::distance(db.cbegin(), db.cend()); + eosio::print("db size:", uint64_t(db_size), "\n"); + itr = accounts.erase(itr); + } + + account_code_table codes(_self, _self.value); + auto itrc = codes.begin(); + while(itrc != codes.end()) { + itrc = codes.erase(itrc); + } + + gc(std::numeric_limits::max()); + + auto account_size = std::distance(accounts.cbegin(), accounts.cend()); + eosio::print("accounts size:", uint64_t(account_size), "\n"); + + eosio::print("CLEAR end\n"); +} + +[[eosio::action]] void evm_contract::updatecode( const bytes& address, uint64_t incarnation, const bytes& code_hash, const bytes& code) { + assert_unfrozen(); + + eosio::require_auth(get_self()); + + evm_runtime::state state{get_self(), get_self()}; + auto bvcode = ByteView{(const uint8_t *)code.data(), code.size()}; + state.update_account_code(to_address(address), incarnation, to_bytes32(code_hash), bvcode); +} + +[[eosio::action]] void evm_contract::updatestore(const bytes& address, uint64_t incarnation, const bytes& location, const bytes& initial, const bytes& current) { + assert_unfrozen(); + + eosio::require_auth(get_self()); + + evm_runtime::state state{get_self(), get_self()}; + eosio::print("updatestore: "); + eosio::printhex(address.data(), address.size()); + eosio::print("\n "); + eosio::printhex(location.data(), location.size()); + eosio::print(":"); + eosio::printhex(current.data(), current.size()); + eosio::print("\n"); + + state.update_storage(to_address(address), incarnation, to_bytes32(location), to_bytes32(initial), to_bytes32(current)); +} + +[[eosio::action]] void evm_contract::updateaccnt(const bytes& address, const bytes& initial, const bytes& current) { + assert_unfrozen(); + + eosio::require_auth(get_self()); + + evm_runtime::state state{get_self(), get_self()}; + auto maybe_account = [](const bytes& data) -> std::optional { + std::optional res{}; + if(data.size()) { + Account tmp; + ByteView bv{(const uint8_t *)data.data(), data.size()}; + auto dec_res = Account::from_encoded_storage(bv); + eosio::check(!!dec_res, "unable to decode account"); + res = *dec_res; + } + return res; + }; + + auto oinitial = maybe_account(initial); + auto ocurrent = maybe_account(current); + + state.update_account(to_address(address), oinitial, ocurrent); +} + +[[eosio::action]] void evm_contract::setbal(const bytes& addy, const bytes& bal) { + assert_unfrozen(); + + eosio::require_auth(get_self()); + + account_table accounts(_self, _self.value); + auto inx = accounts.get_index<"by.address"_n>(); + auto itr = inx.find(make_key(addy)); + + if(itr == inx.end()) { + accounts.emplace(get_self(), [&](auto& row){ + row.id = accounts.available_primary_key();; + row.code_id = std::nullopt; + row.eth_address = addy; + row.balance = bal; + }); + } else { + accounts.modify(*itr, eosio::same_payer, [&](auto& row){ + row.balance = bal; + }); + } +} + +[[eosio::action]] void evm_contract::testbaldust(const name test) { + if(test == "basic"_n) { + balance_with_dust b; + // ↱minimum EOS + // .123456789abcdefghi EEOS + b += 200000000_u256; //adds to dust only + b += 3000_u256; //adds to dust only + b += 100000000000000_u256; //adds strictly to balance + b += 200000000007000_u256; //adds to both balance and dust + b += 60000000000000_u256; //adds to dust only + b += 55000000000000_u256; //adds to dust only but dust overflows +1 to balance + + //expect: 415000200010000; .0004 EOS, 15000200010000 dust + check(b.balance.amount == 4, ""); + check(b.dust == 15000200010000, ""); + + // ↱minimum EOS + // .123456789abcdefghi EEOS + b -= 45_u256; //substracts from dust only + b -= 100000000000000_u256; //subtracts strictly from balance + b -= 120000000000000_u256; //subtracts from both dust and balance, causes underflow on dust thus -1 balance + + //expect: 195000200009955; .0001 EOS, 95000200009955 dust + check(b.balance.amount == 1, ""); + check(b.dust == 95000200009955, ""); + } + else if(test == "underflow1"_n) { + balance_with_dust b; + // ↱minimum EOS + // .123456789abcdefghi EEOS + b -= 45_u256; + //should fail with underflow on dust causing an underflow of balance + } + else if(test == "underflow2"_n) { + balance_with_dust b; + // ↱minimum EOS + // .123456789abcdefghi EEOS + b -= 100000000000000_u256; + //should fail with underflow on balance + } + else if(test == "underflow3"_n) { + balance_with_dust b; + // ↱minimum EOS + // .123456789abcdefghi EEOS + b += 200000000000000_u256; + b -= 300000000000000_u256; + //should fail with underflow on balance + } + else if(test == "underflow4"_n) { + balance_with_dust b; + // ↱minimum EOS + // .123456789abcdefghi EEOS + b += 50000_u256; + b -= 500000000_u256; + //should fail with underflow on dust causing an underflow of balance + } + else if(test == "underflow5"_n) { + balance_with_dust b; + // do a decrement that would overflow an int64_t but not uint64_t (for balance) + // ↱int64t max ↱minimum EOS + // 9223372036854775807 (2^63)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 50000_u256; + b -= 100000000000000000000000000000000_u256; + //should fail with underflow + } + else if(test == "overflow1"_n) { + balance_with_dust b; + // increment a value that would overflow a int64_t, but not uint64_t + // ↱int64t max ↱minimum EOS + // 9223372036854775807 (2^63)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 1000000000000000000000000000000000_u256; + //should fail with overflow + } + else if(test == "overflow2"_n) { + balance_with_dust b; + // increment a value that would overflow a max_asset, but not an int64_t + // ↱max_asset max ↱minimum EOS + // 4611686018427387903 (2^62)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 500000000000000000000000000000000_u256; + //should fail with overflow + } + else if(test == "overflow3"_n || test == "overflow4"_n || test == "overflow5"_n || test == "overflowa"_n || test == "overflowb"_n || test == "overflowc"_n) { + balance_with_dust b; + // start with a value that should be the absolute max allowed + // ↱max_asset max ↱minimum EOS + // 4611686018427387903 (2^62)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 461168601842738790399999999999999_u256; + if(test == "overflow4"_n) { + //add 1 to balance, should fail since it rolls balance over max_asset + // ↱minimum EOS + // .123456789abcdefghi EEOS + b += 100000000000000_u256; + //should fail with overflow + } + if(test == "overflow5"_n) { + //add 1 to dust, causing a rollover making balance > max_asset + // ↱minimum EOS + // .123456789abcdefghi EEOS + b += 1_u256; + //should fail with overflow + } + if(test == "overflowa"_n) { + //add something huge + // ↱max_asset max ↱minimum EOS + // 4611686018427387903 (2^62)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 999461168601842738790399999999999999_u256; + //should fail with overflow + } + if(test == "overflowb"_n) { + // add max allowed to balance again; this should be a 2^62-1 + 2^62-1 + // ↱max_asset max ↱minimum EOS + // 4611686018427387903 (2^62)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 461168601842738790300000000000000_u256; + //should fail with overflow + } + if(test == "overflowc"_n) { + // add max allowed to balance again; but also with max dust; should be a 2^62-1 + 2^62-1 + 1 on asset balance + // ↱max_asset max ↱minimum EOS + // 4611686018427387903 (2^62)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 461168601842738790399999999999999_u256; + //should fail with overflow + } + } + if(test == "overflowd"_n) { + balance_with_dust b; + //add something massive + // ↱max_asset max ↱minimum EOS + // 4611686018427387903 (2^62)-1 + // 543210987654321̣123456789abcdefghi EEOS + b += 99999999461168601842738790399999999999999_u256; + //should fail with overflow + } +} + +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3bf9092e..707b21f9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ add_eosio_test_executable( unit_test ${CMAKE_SOURCE_DIR}/call_tests.cpp ${CMAKE_SOURCE_DIR}/chainid_tests.cpp ${CMAKE_SOURCE_DIR}/bridge_message_tests.cpp + ${CMAKE_SOURCE_DIR}/admin_actions_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/admin_actions_tests.cpp b/tests/admin_actions_tests.cpp new file mode 100644 index 00000000..ed89c498 --- /dev/null +++ b/tests/admin_actions_tests.cpp @@ -0,0 +1,448 @@ +#include + +#include "basic_evm_tester.hpp" +#include +#include "utils.hpp" + +using namespace evm_test; +using eosio::testing::eosio_assert_message_is; +using eosio::testing::expect_assert_message; + +struct vault_balance_row; + +struct admin_action_tester : basic_evm_tester { + admin_action_tester() { + create_accounts({"alice"_n}); + transfer_token(faucet_account_name, "alice"_n, make_asset(10000'0000)); + init(); + } + + intx::uint256 getval(const evmc::address& contract_addr) { + exec_input input; + input.context = {}; + input.to = bytes{std::begin(contract_addr.bytes), std::end(contract_addr.bytes)}; + + silkworm::Bytes data; + data += evmc::from_hex("31b6bd06").value(); // sha3(getval())[:4] + input.data = bytes{data.begin(), data.end()}; + + auto res = exec(input, {}); + BOOST_REQUIRE(res); + BOOST_REQUIRE(res->action_traces.size() == 1); + + auto out = fc::raw::unpack(res->action_traces[0].return_value); + BOOST_REQUIRE(out.status == 0); + BOOST_REQUIRE(out.data.size() == 32); + auto val = intx::be::unsafe::load(reinterpret_cast(out.data.data())); + + dlog("GETVAL: ${v}", ("v", static_cast(val))); + return val; + } + + auto deploy_simple_contract(evm_eoa& evm_account) { + + // // SPDX-License-Identifier: GPL-3.0 + // pragma solidity >=0.7.0 <0.9.0; + // contract Simple { + // uint256 val; + // address payable public owner; + // constructor() { + // owner = payable(msg.sender); + // } + // function setval(uint256 v) public { + // val=v; + // } + // function getval() public view returns (uint256) { + // return val; + // } + // function killme() public { + // selfdestruct(owner); + // } + // } + + const std::string simple_bytecode = "608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061024b806100616000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806324d97a4a1461005157806331b6bd061461005b578063559c9c4a146100795780638da5cb5b14610095575b600080fd5b6100596100b3565b005b6100636100ee565b6040516100709190610140565b60405180910390f35b610093600480360381019061008e919061018c565b6100f7565b005b61009d610101565b6040516100aa91906101fa565b60405180910390f35b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60008054905090565b8060008190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b61013a81610127565b82525050565b60006020820190506101556000830184610131565b92915050565b600080fd5b61016981610127565b811461017457600080fd5b50565b60008135905061018681610160565b92915050565b6000602082840312156101a2576101a161015b565b5b60006101b084828501610177565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101e4826101b9565b9050919050565b6101f4816101d9565b82525050565b600060208201905061020f60008301846101eb565b9291505056fea26469706673582212204abac6746f4497f12044fdf4568905d560328e27fa03b3b954fc303865b8429764736f6c63430008120033"; + + // Deploy simple contract + auto contract_addr = deploy_contract(evm_account, evmc::from_hex(simple_bytecode).value()); + uint64_t contract_account_id = find_account_by_address(contract_addr).value().id; + + return std::make_tuple(contract_addr, contract_account_id); + } + + size_t total_gcrows() { + size_t total=0; + scan_gcstore([&total](evm_test::gcstore row) -> bool { + ++total; + return false; + }); + return total; + }; + + size_t total_evm_accounts() { + size_t total=0; + scan_accounts([&total](evm_test::account_object) -> bool { + ++total; + return false; + }); + return total; + }; + + size_t total_account_code() { + size_t total=0; + scan_account_code([&total](evm_test::account_code) -> bool { + ++total; + return false; + }); + return total; + } + +}; + +BOOST_AUTO_TEST_SUITE(admin_actions_tests) +BOOST_FIXTURE_TEST_CASE(rmgcstore_tests, admin_action_tester) try { + + // 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()); + + auto [contract_addr, contract_account_id] = deploy_simple_contract(evm1); + + BOOST_REQUIRE(total_gcrows() == 0); + BOOST_REQUIRE(total_evm_accounts() == 2); + + // Call method "killme" on simple contract (sha3('killme()') = 0x24d97a4a) + auto txn = generate_tx(contract_addr, 0, 500'000); + txn.data = evmc::from_hex("0x24d97a4a").value(); + evm1.sign(txn); + pushtx(txn); + + BOOST_REQUIRE(total_gcrows() == 1); + BOOST_REQUIRE(total_evm_accounts() == 1); + + auto gcs = get_gcstore(0); + BOOST_REQUIRE(gcs.storage_id == contract_account_id); + + BOOST_REQUIRE_EXCEPTION(rmgcstore(0, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + rmgcstore(0, evm_account_name); + + BOOST_REQUIRE(total_gcrows() == 0); + BOOST_REQUIRE(total_evm_accounts() == 1); + + produce_blocks(5); + BOOST_REQUIRE_EXCEPTION(rmgcstore(0, evm_account_name), + eosio_assert_message_exception, eosio_assert_message_is("gc row not found")); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(setkvstore_tests, admin_action_tester) try { + + // 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()); + + auto [contract_addr, contract_account_id] = deploy_simple_contract(evm1); + + // Call method "setval" on simple contract (sha3('setval(uint256)') = 0x559c9c4a) + auto txn = generate_tx(contract_addr, 0, 500'000); + txn.data = evmc::from_hex("0x559c9c4a").value(); + txn.data += evmc::from_hex("0x0000000000000000000000000000000000000000000000000000000000000042").value(); + evm1.sign(txn); + pushtx(txn); + + std::map slots; + auto load_slots = [&]() { + slots.clear(); + scan_account_storage(contract_account_id, [&](storage_slot&& slot) -> bool { + auto key_u64 = static_cast(slot.key); + slots[key_u64] = slot.value; + return false; + }); + }; + + load_slots(); + BOOST_REQUIRE(slots.size() == 2); + BOOST_REQUIRE(slots[0] == intx::uint256(66)); + BOOST_REQUIRE(getval(contract_addr) == intx::uint256(66)); + + BOOST_REQUIRE_EXCEPTION(setkvstore(contract_account_id, to_bytes(intx::uint256(0)), to_bytes(intx::uint256(55)), "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + BOOST_REQUIRE_EXCEPTION(setkvstore(contract_account_id, {}, to_bytes(intx::uint256(55))), + eosio_assert_message_exception, eosio_assert_message_is("invalid key/value size")); + + BOOST_REQUIRE_EXCEPTION(setkvstore(contract_account_id, to_bytes(intx::uint256(0)), bytes{}), + eosio_assert_message_exception, eosio_assert_message_is("invalid key/value size")); + + setkvstore(contract_account_id, to_bytes(intx::uint256(0)), to_bytes(intx::uint256(55))); + + produce_blocks(5); + + load_slots(); + BOOST_REQUIRE(slots.size() == 2); + BOOST_REQUIRE(slots[0] == intx::uint256(55)); + BOOST_REQUIRE(getval(contract_addr) == intx::uint256(55)); + + BOOST_REQUIRE_EXCEPTION(setkvstore(contract_account_id, to_bytes(intx::uint256(111)), {}), + eosio_assert_message_exception, eosio_assert_message_is("key not found")); + + setkvstore(contract_account_id, to_bytes(intx::uint256(0)), {}); + produce_blocks(5); + + load_slots(); + BOOST_REQUIRE(slots.size() == 1); + BOOST_REQUIRE(slots.find(0) == slots.end()); + BOOST_REQUIRE(getval(contract_addr) == intx::uint256(0)); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(rmaccount_tests, admin_action_tester) try { + + // 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()); + + auto [contract_addr, contract_account_id] = deploy_simple_contract(evm1); + auto [contract_addr2, contract_account_id2] = deploy_simple_contract(evm1); + + BOOST_REQUIRE(total_evm_accounts() == 3); + BOOST_REQUIRE(total_account_code() == 1); + BOOST_REQUIRE(total_gcrows() == 0); + + BOOST_REQUIRE_EXCEPTION(rmaccount(contract_account_id, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + rmaccount(contract_account_id); + BOOST_REQUIRE(total_evm_accounts() == 2); + BOOST_REQUIRE(total_account_code() == 1); + BOOST_REQUIRE(total_gcrows() == 1); + BOOST_REQUIRE(get_gcstore(0).storage_id == contract_account_id); + + rmaccount(contract_account_id2); + BOOST_REQUIRE(total_evm_accounts() == 1); + BOOST_REQUIRE(total_account_code() == 0); + BOOST_REQUIRE(total_gcrows() == 2); + BOOST_REQUIRE(get_gcstore(0).storage_id == contract_account_id); + BOOST_REQUIRE(get_gcstore(1).storage_id == contract_account_id2); + + produce_blocks(5); + + BOOST_REQUIRE_EXCEPTION(rmaccount(contract_account_id), + eosio_assert_message_exception, eosio_assert_message_is("account not found")); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(freezeaccnt_tests, admin_action_tester) try { + + // 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()); + + auto evm1_account = find_account_by_address(evm1.address).value(); + BOOST_REQUIRE( evm1_account.has_flag(evm_test::account_object::flag::frozen) == false ); + + BOOST_REQUIRE_EXCEPTION(freezeaccnt(evm1_account.id, true, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + freezeaccnt(evm1_account.id, true); + evm1_account = find_account_by_address(evm1.address).value(); + + BOOST_REQUIRE( evm1_account.has_flag(evm_test::account_object::flag::frozen) == true ); + + BOOST_REQUIRE_EXCEPTION(deploy_simple_contract(evm1), + eosio_assert_message_exception, eosio_assert_message_is("account is frozen")); + evm1.next_nonce--; + + freezeaccnt(evm1_account.id, false); + evm1_account = find_account_by_address(evm1.address).value(); + + BOOST_REQUIRE( evm1_account.has_flag(evm_test::account_object::flag::frozen) == false ); + + auto [contract_addr, contract_account_id] = deploy_simple_contract(evm1); + freezeaccnt(contract_account_id, true); + + // We allow evm::exec action to work with frozen accounts + BOOST_REQUIRE(getval(contract_addr) == 0); + + BOOST_REQUIRE_EXCEPTION(transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), fc::variant(contract_addr).as_string()), + eosio_assert_message_exception, eosio_assert_message_is("account is frozen")); + + auto txn = generate_tx(contract_addr, 1_ether, 21'000); + evm1.sign(txn); + BOOST_REQUIRE_EXCEPTION(pushtx(txn), + eosio_assert_message_exception, eosio_assert_message_is("account is frozen")); + + freezeaccnt(contract_account_id, false); + pushtx(txn); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(addevmbal_subtract_tests, admin_action_tester) try { + + // 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()); + auto evm1_account = find_account_by_address(evm1.address).value(); + + BOOST_REQUIRE_EXCEPTION(addevmbal(evm1_account.id, 1_ether, true, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + BOOST_REQUIRE_EXCEPTION(addevmbal(2, 1_ether, true), + eosio_assert_message_exception, eosio_assert_message_is("account not found")); + + auto b100_plus_one = 100_ether + 1; + + // Error when decrementing inevm + BOOST_REQUIRE_EXCEPTION(addevmbal(evm1_account.id, b100_plus_one, true), + eosio_assert_message_exception, eosio_assert_message_is("decrementing more than available")); + + // Fund evm2 address with 0.0001 EOS + evm_eoa evm2; + const int64_t to_bridge2 = 1; + transfer_token("alice"_n, evm_account_name, make_asset(to_bridge2), evm2.address_0x()); + auto evm2_account = find_account_by_address(evm2.address).value(); + + // inevm: 100 eth (evm1) + 10^14 wei (evm2) + + // Error when decrementing address balance (carry check) + BOOST_REQUIRE_EXCEPTION(addevmbal(evm1_account.id, b100_plus_one, true), + eosio_assert_message_exception, eosio_assert_message_is("underflow detected")); + + check_balances(); + + //substract 10^14 wei to evm1 + intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(18 - 4)); + + addevmbal(evm1_account.id, minimum_natively_representable, true); + + // This will fail since the eosio.token balance of `evm_account_name` is 0.0001 greater than the sum of all balances (evm + open balances) + BOOST_REQUIRE_THROW(check_balances(), std::runtime_error); + + // Send 0.0001 EOS from `evm_account_name` to alice + transfer_token(evm_account_name, "alice"_n, make_asset(1)); + + check_balances(); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(addevmbal_add_tests, admin_action_tester) try { + + // 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()); + auto evm1_account = find_account_by_address(evm1.address).value(); + + BOOST_REQUIRE_EXCEPTION(addevmbal(evm1_account.id, 1_ether, false, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + BOOST_REQUIRE_EXCEPTION(addevmbal(2, 1_ether, false), + eosio_assert_message_exception, eosio_assert_message_is("account not found")); + + intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(18 - 4)); + + //error when incrementing address balance (carry check) + intx::uint256 v = std::numeric_limits::max() - to_bridge*minimum_natively_representable + 1; + BOOST_REQUIRE_EXCEPTION(addevmbal(evm1_account.id, v, false), + eosio_assert_message_exception, eosio_assert_message_is("overflow detected")); + + //error when incrementing inevm + v = intx::uint256(asset::max_amount - to_bridge + 1) * minimum_natively_representable; + BOOST_REQUIRE_EXCEPTION(addevmbal(evm1_account.id, v, false), + eosio_assert_message_exception, eosio_assert_message_is("accumulation overflow")); + + check_balances(); + + // add 0.0001 EOS to evm1 + v = 1 * minimum_natively_representable; + + // inevm: 100.0000 EOS + // evm1: 100.0000 EOS + // `evm_account_name` 1.0000 EOS + // eosio.token balance: 101.0000 EOS + + addevmbal(evm1_account.id, v, false); + + // inevm: 100.0001 EOS + // evm1: 100.0001 EOS + // `evm_account_name` 1.0000 EOS + // eosio.token balance: 101.0000 EOS + + // This will fail since the eosio.token balance of `evm_account_name` is 0.0001 less than the sum of all balances (evm + open balances) + BOOST_REQUIRE_THROW(check_balances(), std::runtime_error); + + // send 0.0001 EOS from alice to `evm_account_name` (to be credited to `evm_account_name`) + transfer_token("alice"_n, evm_account_name, make_asset(1), evm_account_name.to_string()); + + // inevm: 100.0001 EOS + // evm1: 100.0001 EOS + // `evm_account_name` 1.0001 EOS + // eosio.token balance: 101.0001 EOS + + // This will fail since the 0.0001 EOS sent in the previous transfer has been credited to: + // - the open balance of `evm_account_name` + // - the eosio.token balance of `evm_account_name` + BOOST_REQUIRE_THROW(check_balances(), std::runtime_error); + + // We need to substract 0.0001 from the open balance of `evm_account_name` + addopenbal(evm_account_name, minimum_natively_representable, true); + + check_balances(); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(addopenbal_tests, admin_action_tester) try { + open("alice"_n); + transfer_token("alice"_n, evm_account_name, make_asset(100'0000), "alice"); + + intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(18 - 4)); + + BOOST_REQUIRE_EXCEPTION(addopenbal("beto"_n, intx::uint256(100'0000)*minimum_natively_representable, false, "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + BOOST_REQUIRE_EXCEPTION(addopenbal("beto"_n, intx::uint256(100'0000)*minimum_natively_representable, false), + eosio_assert_message_exception, eosio_assert_message_is("account not found")); + + BOOST_REQUIRE_EXCEPTION(addopenbal("alice"_n, intx::uint256(100'0001)*minimum_natively_representable, true), + eosio_assert_message_exception, eosio_assert_message_is("decrementing more than available")); + + BOOST_REQUIRE_EXCEPTION(addopenbal("alice"_n, std::numeric_limits::max()-intx::uint256(99'9999)*minimum_natively_representable, false), + eosio_assert_message_exception, eosio_assert_message_is("accumulation overflow")); + + check_balances(); + + addopenbal("alice"_n, minimum_natively_representable, true); + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == intx::uint256(99'9999)*minimum_natively_representable); + + // This will fail since the eosio.token balance of `evm_account_name` is 0.0001 greater than the sum of all balances (evm + open balances) + BOOST_REQUIRE_THROW(check_balances(), std::runtime_error); + + // send 0.0001 EOS from `evm_account_name` to alice + transfer_token(evm_account_name, "alice"_n, make_asset(1)); + + check_balances(); + + addopenbal("alice"_n, minimum_natively_representable, false); + BOOST_REQUIRE(intx::uint256(vault_balance("alice"_n)) == intx::uint256(100'0000)*minimum_natively_representable); + + // This will fail since the eosio.token balance of `evm_account_name` is 0.0001 less than the sum of all balances (evm + open balances) + BOOST_REQUIRE_THROW(check_balances(), std::runtime_error); + produce_blocks(1); + + // This combination is to adjust just the eosio.token EOS balance of the `evm_account_name` + transfer_token("alice"_n, evm_account_name, make_asset(1), evm_account_name.to_string()); + addopenbal(evm_account_name, minimum_natively_representable, true); + + check_balances(); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/basic_evm_tester.cpp b/tests/basic_evm_tester.cpp index 1a62b68c..f25dfe40 100644 --- a/tests/basic_evm_tester.cpp +++ b/tests/basic_evm_tester.cpp @@ -44,6 +44,8 @@ struct partial_account_table_row bytes eth_address; uint64_t nonce; bytes balance; + std::optional code_id; + uint32_t flags; }; struct storage_table_row @@ -55,8 +57,23 @@ struct storage_table_row } // namespace evm_test +namespace fc { namespace raw { + template<> + inline void unpack( datastream& ds, evm_test::partial_account_table_row& tmp) + { try { + fc::raw::unpack(ds, tmp.id); + fc::raw::unpack(ds, tmp.eth_address); + fc::raw::unpack(ds, tmp.nonce); + fc::raw::unpack(ds, tmp.balance); + fc::raw::unpack(ds, tmp.code_id); + tmp.flags=0; + if(ds.remaining()) { fc::raw::unpack(ds, tmp.flags); } + } FC_RETHROW_EXCEPTIONS(warn, "error unpacking partial_account_table_row") } +}} + + FC_REFLECT(evm_test::vault_balance_row, (owner)(balance)(dust)) -FC_REFLECT(evm_test::partial_account_table_row, (id)(eth_address)(nonce)(balance)) +FC_REFLECT(evm_test::partial_account_table_row, (id)(eth_address)(nonce)(balance)(code_id)(flags)) FC_REFLECT(evm_test::storage_table_row, (id)(key)(value)) namespace evm_test { @@ -259,6 +276,13 @@ config2_table_row basic_evm_tester::get_config2() const return fc::raw::unpack(d); } +gcstore basic_evm_tester::get_gcstore(uint64_t id) const +{ + const vector d = get_row_by_account(evm_account_name, evm_account_name, "gcstore"_n, name{id}); + FC_ASSERT(d.size(), "not found"); + return fc::raw::unpack(d); +} + void basic_evm_tester::setfeeparams(const fee_parameters& fee_params) { mvo fee_params_vo; @@ -371,6 +395,38 @@ transaction_trace_ptr basic_evm_tester::pushtx(const silkworm::Transaction& trx, return push_action(evm_account_name, "pushtx"_n, miner, mvo()("miner", miner)("rlptx", rlp_bytes)); } +transaction_trace_ptr basic_evm_tester::rmgcstore(uint64_t id, name actor) { + return basic_evm_tester::push_action(evm_account_name, "rmgcstore"_n, actor, + mvo()("id", id)); +} + +transaction_trace_ptr basic_evm_tester::setkvstore(uint64_t account_id, const bytes& key, const std::optional& value, name actor) { + return basic_evm_tester::push_action(evm_account_name, "setkvstore"_n, actor, + mvo()("account_id", account_id)("key", key)("value", value)); +} + +transaction_trace_ptr basic_evm_tester::rmaccount(uint64_t id, name actor) { + return basic_evm_tester::push_action(evm_account_name, "rmaccount"_n, actor, + mvo()("id", id)); +} + +transaction_trace_ptr basic_evm_tester::freezeaccnt(uint64_t id, bool value, name actor) { + return basic_evm_tester::push_action(evm_account_name, "freezeaccnt"_n, actor, + mvo()("id", id)("value",value)); +} + +transaction_trace_ptr basic_evm_tester::addevmbal(uint64_t id, const intx::uint256& delta, bool subtract, name actor) { + auto d = to_bytes(delta); + return basic_evm_tester::push_action(evm_account_name, "addevmbal"_n, actor, + mvo()("id", id)("delta",d)("subtract",subtract)); +} + +transaction_trace_ptr basic_evm_tester::addopenbal(name account, const intx::uint256& delta, bool subtract, name actor) { + auto d = to_bytes(delta); + return basic_evm_tester::push_action(evm_account_name, "addopenbal"_n, actor, + mvo()("account", account)("delta",d)("subtract",subtract)); +} + evmc::address basic_evm_tester::deploy_contract(evm_eoa& eoa, evmc::bytes bytecode) { uint64_t nonce = eoa.next_nonce; @@ -461,6 +517,8 @@ std::optional convert_to_account_object(const partial_account_ta .address = std::move(address), .nonce = row.nonce, .balance = intx::be::unsafe::load(reinterpret_cast(row.balance.data())), + .code_id = row.code_id, + .flags = row.flags }; } @@ -586,6 +644,28 @@ void basic_evm_tester::scan_balances(std::function visi ); } +bool basic_evm_tester::scan_gcstore(std::function visitor) const +{ + static constexpr eosio::chain::name gcstore_table_name = "gcstore"_n; + + scan_table( + gcstore_table_name, evm_account_name, [&visitor](gcstore&& row) { return visitor(row); } + ); + + return true; +} + +bool basic_evm_tester::scan_account_code(std::function visitor) const +{ + static constexpr eosio::chain::name account_code_table_name = "accountcode"_n; + + scan_table( + account_code_table_name, evm_account_name, [&visitor](account_code&& row) { return visitor(row); } + ); + + return true; +} + 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); @@ -593,13 +673,16 @@ asset basic_evm_tester::get_eos_balance( const account_name& act ) { void basic_evm_tester::check_balances() { intx::uint256 total_in_evm_accounts; - scan_accounts([&](account_object&& account) -> bool { + scan_accounts([&](evm_test::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); + if(total_in_evm_accounts != in_evm) { + dlog("inevm: ${inevm}, total_in_evm_accounts: ${total_in_evm_accounts}",("total_in_evm_accounts",intx::to_string(total_in_evm_accounts))("inevm",intx::to_string(in_evm))); + throw std::runtime_error("total_in_evm_accounts != in_evm"); + } intx::uint256 total_in_accounts; scan_balances([&](vault_balance_row&& row) -> bool { @@ -608,7 +691,10 @@ void basic_evm_tester::check_balances() { }); 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); + if(evm_eos_balance != total_in_accounts+total_in_evm_accounts) { + dlog("evm_eos_balance: ${evm_eos_balance}, total_in_accounts+total_in_evm_accounts: ${tt}",("tt",intx::to_string(total_in_accounts+total_in_evm_accounts))("evm_eos_balance",intx::to_string(evm_eos_balance))); + throw std::runtime_error("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 c11ab9e1..7a6b4c62 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -24,6 +24,9 @@ using namespace eosio; using namespace eosio::chain; using mvo = fc::mutable_variant_object; +#include "utils.hpp" +namespace evm_test { struct vault_balance_row; } + using intx::operator""_u256; namespace intx { @@ -74,10 +77,20 @@ struct balance_and_dust struct account_object { + enum class flag : uint32_t { + frozen = 0x1 + }; + uint64_t id; evmc::address address; uint64_t nonce; intx::uint256 balance; + std::optional code_id; + std::optional flags; + + inline bool has_flag(flag f)const { + return (flags.has_value() && flags.value() & static_cast(f) != 0); + } }; struct storage_slot @@ -129,6 +142,18 @@ struct bridge_message_v0 { bytes data; }; +struct gcstore { + uint64_t id; + uint64_t storage_id; +}; + +struct account_code { + uint64_t id; + uint32_t ref_count; + bytes code; + bytes code_hash; +}; + using bridge_message = std::variant; } // namespace evm_test @@ -148,6 +173,8 @@ FC_REFLECT(evm_test::exec_output, (status)(data)(context)) FC_REFLECT(evm_test::message_receiver, (account)(handler)(min_fee)(flags)); FC_REFLECT(evm_test::bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); +FC_REFLECT(evm_test::gcstore, (id)(storage_id)); +FC_REFLECT(evm_test::account_code, (id)(ref_count)(code)(code_hash)); namespace evm_test { class evm_eoa @@ -239,6 +266,13 @@ class basic_evm_tester : public testing::validating_tester void addegress(const std::vector& accounts); void removeegress(const std::vector& accounts); + transaction_trace_ptr rmgcstore(uint64_t id, name actor=evm_account_name); + transaction_trace_ptr setkvstore(uint64_t account_id, const bytes& key, const std::optional& value, name actor=evm_account_name); + transaction_trace_ptr rmaccount(uint64_t id, name actor=evm_account_name); + transaction_trace_ptr freezeaccnt(uint64_t id, bool value, name actor=evm_account_name); + transaction_trace_ptr addevmbal(uint64_t id, const intx::uint256& delta, bool subtract, name actor=evm_account_name); + transaction_trace_ptr addopenbal(name account, const intx::uint256& delta, bool subtract, name actor=evm_account_name); + void open(name owner); void close(name owner); void withdraw(name owner, asset quantity); @@ -248,7 +282,7 @@ class basic_evm_tester : public testing::validating_tester 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; - + gcstore get_gcstore(uint64_t id) const; asset get_eos_balance( const account_name& act ); void check_balances(); @@ -283,7 +317,9 @@ class basic_evm_tester : public testing::validating_tester std::optional find_account_by_address(const evmc::address& address) const; std::optional find_account_by_id(uint64_t id) const; bool scan_account_storage(uint64_t account_id, std::function visitor) const; - void scan_balances(std::function visitor) const; + bool scan_gcstore(std::function visitor) const; + bool scan_account_code(std::function visitor) const; + void scan_balances(std::function visitor) const; }; inline constexpr intx::uint256 operator"" _wei(const char* s) { return intx::from_string(s); } diff --git a/tests/utils.hpp b/tests/utils.hpp index ce9f6a4f..6c10fc3c 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -43,4 +43,4 @@ 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 +