From 557a3282b38f731f2252f837442555137a038b3e Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Thu, 24 Aug 2023 19:54:26 -0300 Subject: [PATCH 1/7] Add admin actions --- contract/CMakeLists.txt | 4 + contract/include/evm_runtime/evm_contract.hpp | 9 + contract/include/evm_runtime/tables.hpp | 23 +- contract/src/CMakeLists.txt | 8 +- contract/src/actions.cpp | 372 --------------- contract/src/admin_actions.cpp | 115 +++++ contract/src/state.cpp | 4 +- contract/src/test_actions.cpp | 374 +++++++++++++++ contract/tests/CMakeLists.txt | 1 + contract/tests/admin_actions_tests.cpp | 442 ++++++++++++++++++ contract/tests/basic_evm_tester.cpp | 131 +++++- contract/tests/basic_evm_tester.hpp | 47 +- contract/tests/utils.hpp | 45 ++ 13 files changed, 1196 insertions(+), 379 deletions(-) create mode 100644 contract/src/admin_actions.cpp create mode 100644 contract/src/test_actions.cpp create mode 100644 contract/tests/admin_actions_tests.cpp create mode 100644 contract/tests/utils.hpp diff --git a/contract/CMakeLists.txt b/contract/CMakeLists.txt index 4aa248c9..b64b69a3 100644 --- a/contract/CMakeLists.txt +++ b/contract/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/contract/include/evm_runtime/evm_contract.hpp b/contract/include/evm_runtime/evm_contract.hpp index f6955fba..d0e9b7d8 100644 --- a/contract/include/evm_runtime/evm_contract.hpp +++ b/contract/include/evm_runtime/evm_contract.hpp @@ -84,6 +84,15 @@ class [[eosio::contract]] evm_contract : public contract /// @return true if all garbage has been collected [[eosio::action]] bool gc(uint32_t max); +#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 asset& delta); + [[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/contract/include/evm_runtime/tables.hpp b/contract/include/evm_runtime/tables.hpp index d8639772..8e4267f9 100644 --- a/contract/include/evm_runtime/tables.hpp +++ b/contract/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/contract/src/CMakeLists.txt b/contract/src/CMakeLists.txt index 362b409d..9931e4f0 100644 --- a/contract/src/CMakeLists.txt +++ b/contract/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.5.1") @@ -84,5 +90,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=46080) + target_link_options(evm_runtime PUBLIC --stack-size=45360) endif() diff --git a/contract/src/actions.cpp b/contract/src/actions.cpp index 45380030..d8428563 100644 --- a/contract/src/actions.cpp +++ b/contract/src/actions.cpp @@ -14,11 +14,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 @@ -556,371 +551,4 @@ bool evm_contract::gc(uint32_t max) { return state.gc(max); } -#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::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) == DecodingResult::kOk && bv.empty(), "unable to decode transaction"); - - execute_tx(eosio::name{}, block, tx, ep, false); - } - engine.finalize(ep.state(), ep.evm().block(), ep.evm().revision()); - 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.second == DecodingResult::kOk, "unable to decode account"); - res = dec_res.first; - } - 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/contract/src/admin_actions.cpp b/contract/src/admin_actions.cpp new file mode 100644 index 00000000..f4839725 --- /dev/null +++ b/contract/src/admin_actions.cpp @@ -0,0 +1,115 @@ +#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); + } + } + + 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 asset& delta) { + 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 res = itr->balance.balance + delta; + eosio::check(res.amount >= 0, "negative final balance"); + + open_balances.modify(*itr, eosio::same_payer, [&](auto& row){ + row.balance.balance = res; + }); +} + +[[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/contract/src/state.cpp b/contract/src/state.cpp index f9c253cd..27ec48a8 100644 --- a/contract/src/state.cpp +++ b/contract/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(!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/contract/src/test_actions.cpp b/contract/src/test_actions.cpp new file mode 100644 index 00000000..6e802f35 --- /dev/null +++ b/contract/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/contract/tests/CMakeLists.txt b/contract/tests/CMakeLists.txt index f093ca4e..a1ffb7f4 100644 --- a/contract/tests/CMakeLists.txt +++ b/contract/tests/CMakeLists.txt @@ -35,6 +35,7 @@ add_eosio_test_executable( unit_test ${CMAKE_SOURCE_DIR}/blockhash_tests.cpp ${CMAKE_SOURCE_DIR}/exec_tests.cpp ${CMAKE_SOURCE_DIR}/chainid_tests.cpp + ${CMAKE_SOURCE_DIR}/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/contract/tests/admin_actions_tests.cpp b/contract/tests/admin_actions_tests.cpp new file mode 100644 index 00000000..0278b3cd --- /dev/null +++ b/contract/tests/admin_actions_tests.cpp @@ -0,0 +1,442 @@ +#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_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); + + auto total_gcrows = [&]() -> size_t { + size_t total=0; + scan_gcstore([&total](evm_test::gcstore row) -> bool { + ++total; + return false; + }); + return total; + }; + + 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_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); + + rmaccount(contract_account_id2); + BOOST_REQUIRE(total_evm_accounts() == 1); + BOOST_REQUIRE(total_account_code() == 0); + + 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); + + BOOST_REQUIRE_EXCEPTION(getval(contract_addr), + eosio_assert_message_exception, eosio_assert_message_is("account is frozen")); + + 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, make_asset(-1)); + + 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"); + + BOOST_REQUIRE_EXCEPTION(addopenbal("beto"_n, make_asset(100'0000), "alice"_n), + missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + + BOOST_REQUIRE_EXCEPTION(addopenbal("beto"_n, make_asset(100'0000)), + eosio_assert_message_exception, eosio_assert_message_is("account not found")); + + BOOST_REQUIRE_EXCEPTION(addopenbal("alice"_n, make_asset(-100'0001)), + eosio_assert_message_exception, eosio_assert_message_is("negative final balance")); + + BOOST_REQUIRE_EXCEPTION(addopenbal("alice"_n, make_asset(asset::max_amount-99'9999)), + eosio_assert_message_exception, eosio_assert_message_is("addition overflow")); + + check_balances(); + + intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(18 - 4)); + + addopenbal("alice"_n, make_asset(-1)); + 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, make_asset(1)); + 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 alice + transfer_token("alice"_n, evm_account_name, make_asset(1), evm_account_name.to_string()); + addopenbal("alice"_n, make_asset(-1)); + + check_balances(); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/contract/tests/basic_evm_tester.cpp b/contract/tests/basic_evm_tester.cpp index 062d1429..87019b97 100644 --- a/contract/tests/basic_evm_tester.cpp +++ b/contract/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,24 @@ 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 { + dlog("enrtro en partial_account_table_row unpack"); + 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 { @@ -255,6 +273,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; @@ -299,6 +324,37 @@ 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()}); } +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 asset& delta, name actor) { + return basic_evm_tester::push_action(evm_account_name, "addopenbal"_n, actor, + mvo()("account", account)("delta",delta)); +} + void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) { silkworm::Bytes rlp; @@ -397,6 +453,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 }; } @@ -513,4 +571,73 @@ bool basic_evm_tester::scan_account_storage(uint64_t account_id, 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; +} + +//TODO: remove when merging +//---begin +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)); +} + +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::scan_balances(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); + } + ); +} + +void basic_evm_tester::check_balances() { + intx::uint256 total_in_evm_accounts; + scan_accounts([&](evm_test::account_object&& account) -> bool { + total_in_evm_accounts += account.balance; + return false; + }); + + auto in_evm = intx::uint256(inevm()); + 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 { + 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}); + 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"); + } +} +//---end + +} // namespace evm_test diff --git a/contract/tests/basic_evm_tester.hpp b/contract/tests/basic_evm_tester.hpp index cd83b0ef..4851cf1b 100644 --- a/contract/tests/basic_evm_tester.hpp +++ b/contract/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 @@ -114,6 +127,18 @@ struct exec_output { std::optional context; }; +struct gcstore { + uint64_t id; + uint64_t storage_id; +}; + +struct account_code { + uint64_t id; + uint32_t ref_count; + bytes code; + bytes code_hash; +}; + } // namespace evm_test @@ -128,7 +153,8 @@ FC_REFLECT(evm_test::fee_parameters, (gas_price)(miner_cut)(ingress_bridge_fee)) 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::gcstore, (id)(storage_id)); +FC_REFLECT(evm_test::account_code, (id)(ref_count)(code)(code_hash)); namespace evm_test { class evm_eoa { @@ -212,6 +238,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 asset& delta, name actor=evm_account_name); + void open(name owner); void close(name owner); void withdraw(name owner, asset quantity); @@ -221,6 +254,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; template void scan_table(eosio::chain::name table_name, eosio::chain::name scope_name, Visitor&& visitor) const @@ -252,6 +286,17 @@ 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; + bool scan_gcstore(std::function visitor) const; + bool scan_account_code(std::function visitor) const; + + //TODO: remove before merge + //---begin + balance_and_dust inevm() const; + asset get_eos_balance( const account_name& act ); + void scan_balances(std::function visitor) const; + void check_balances(); + //---end + }; inline constexpr intx::uint256 operator"" _wei(const char* s) { return intx::from_string(s); } diff --git a/contract/tests/utils.hpp b/contract/tests/utils.hpp new file mode 100644 index 00000000..33f53167 --- /dev/null +++ b/contract/tests/utils.hpp @@ -0,0 +1,45 @@ +#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); From bf7a792c30601f78356b2f7029134fc4dc6d77ce Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 30 Aug 2023 14:28:42 -0300 Subject: [PATCH 2/7] fix test_actions.cpp --- contract/src/test_actions.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contract/src/test_actions.cpp b/contract/src/test_actions.cpp index 6e802f35..dcdfa02d 100644 --- a/contract/src/test_actions.cpp +++ b/contract/src/test_actions.cpp @@ -16,18 +16,18 @@ using namespace silkworm; Block block; block.header = bi.get_block_header(); - evm_runtime::test::engine engine{evm_runtime::test::kTestNetwork}; + evm_runtime::test::engine engine; 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"); + eosio::check(rlp::decode(bv,tx) == DecodingResult::kOk && bv.empty(), "unable to decode transaction"); execute_tx(eosio::name{}, block, tx, ep, false); } - engine.finalize(ep.state(), ep.evm().block()); + engine.finalize(ep.state(), ep.evm().block(), ep.evm().revision()); ep.state().write_to_db(ep.evm().block().header.number); } @@ -195,8 +195,8 @@ using namespace silkworm; 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; + eosio::check(dec_res.second == DecodingResult::kOk, "unable to decode account"); + res = dec_res.first; } return res; }; From 15881e40e2f7fb691478e795ca92f243e6120b05 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 30 Aug 2023 17:57:13 -0300 Subject: [PATCH 3/7] admin actions: add removed account id to gcstore --- contract/src/admin_actions.cpp | 6 ++++++ contract/tests/admin_actions_tests.cpp | 24 +++++++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/contract/src/admin_actions.cpp b/contract/src/admin_actions.cpp index f4839725..459b6b27 100644 --- a/contract/src/admin_actions.cpp +++ b/contract/src/admin_actions.cpp @@ -55,6 +55,12 @@ namespace evm_runtime { } } + 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); } diff --git a/contract/tests/admin_actions_tests.cpp b/contract/tests/admin_actions_tests.cpp index 0278b3cd..67df5c76 100644 --- a/contract/tests/admin_actions_tests.cpp +++ b/contract/tests/admin_actions_tests.cpp @@ -69,6 +69,15 @@ struct admin_action_tester : basic_evm_tester { 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 { @@ -99,15 +108,6 @@ BOOST_FIXTURE_TEST_CASE(rmgcstore_tests, admin_action_tester) try { auto [contract_addr, contract_account_id] = deploy_simple_contract(evm1); - auto total_gcrows = [&]() -> size_t { - size_t total=0; - scan_gcstore([&total](evm_test::gcstore row) -> bool { - ++total; - return false; - }); - return total; - }; - BOOST_REQUIRE(total_gcrows() == 0); BOOST_REQUIRE(total_evm_accounts() == 2); @@ -211,6 +211,7 @@ BOOST_FIXTURE_TEST_CASE(rmaccount_tests, admin_action_tester) try { 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")); @@ -218,10 +219,15 @@ BOOST_FIXTURE_TEST_CASE(rmaccount_tests, admin_action_tester) try { 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); From e49990b53e1362582f1f37013e1b880f16773cdd Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 30 Aug 2023 20:31:11 -0300 Subject: [PATCH 4/7] evm_runtime: Allow exec action to work with frozen accounts --- contract/include/evm_runtime/state.hpp | 3 ++- contract/src/actions.cpp | 4 ++-- contract/src/state.cpp | 2 +- contract/tests/admin_actions_tests.cpp | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/contract/include/evm_runtime/state.hpp b/contract/include/evm_runtime/state.hpp index 0cb4a53b..ddc3383d 100644 --- a/contract/include/evm_runtime/state.hpp +++ b/contract/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/contract/src/actions.cpp b/contract/src/actions.cpp index d8428563..0d5a4541 100644 --- a/contract/src/actions.cpp +++ b/contract/src/actions.cpp @@ -341,7 +341,7 @@ void evm_contract::exec(const exec_input& input, const std::optionalsecond}; - 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; diff --git a/contract/src/state.cpp b/contract/src/state.cpp index 27ec48a8..fab0e0ca 100644 --- a/contract/src/state.cpp +++ b/contract/src/state.cpp @@ -16,7 +16,7 @@ std::optional state::read_account(const evmc::address& address) const n if (itr == inx.end()) { return {}; } - eosio::check(!itr->has_flag(account::flag::frozen), "account is frozen"); + eosio::check(_allow_frozen || !itr->has_flag(account::flag::frozen), "account is frozen"); addr2id[address] = itr->id; diff --git a/contract/tests/admin_actions_tests.cpp b/contract/tests/admin_actions_tests.cpp index 67df5c76..95331719 100644 --- a/contract/tests/admin_actions_tests.cpp +++ b/contract/tests/admin_actions_tests.cpp @@ -267,8 +267,8 @@ BOOST_FIXTURE_TEST_CASE(freezeaccnt_tests, admin_action_tester) try { auto [contract_addr, contract_account_id] = deploy_simple_contract(evm1); freezeaccnt(contract_account_id, true); - BOOST_REQUIRE_EXCEPTION(getval(contract_addr), - eosio_assert_message_exception, eosio_assert_message_is("account is frozen")); + // 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")); From 881494985fcd65a1164dd4b0379da59d96c3d858 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Wed, 30 Aug 2023 20:32:15 -0300 Subject: [PATCH 5/7] admin_actions_tests: improve addopenbal_tests --- contract/tests/admin_actions_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contract/tests/admin_actions_tests.cpp b/contract/tests/admin_actions_tests.cpp index 95331719..bcc77d2d 100644 --- a/contract/tests/admin_actions_tests.cpp +++ b/contract/tests/admin_actions_tests.cpp @@ -437,9 +437,9 @@ BOOST_FIXTURE_TEST_CASE(addopenbal_tests, admin_action_tester) try { BOOST_REQUIRE_THROW(check_balances(), std::runtime_error); produce_blocks(1); - // This combination is to adjust just the eosio.token EOS balance of alice + // 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("alice"_n, make_asset(-1)); + addopenbal(evm_account_name, make_asset(-1)); check_balances(); From ab0d99018c8456c7044d1d0eba95fd377bd787f0 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Fri, 1 Sep 2023 17:21:14 -0300 Subject: [PATCH 6/7] admin action: change addopenbal interface --- contract/include/evm_runtime/evm_contract.hpp | 2 +- contract/src/admin_actions.cpp | 11 +++++--- contract/tests/admin_actions_tests.cpp | 26 +++++++++---------- contract/tests/basic_evm_tester.cpp | 5 ++-- contract/tests/basic_evm_tester.hpp | 2 +- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/contract/include/evm_runtime/evm_contract.hpp b/contract/include/evm_runtime/evm_contract.hpp index d0e9b7d8..83387e5a 100644 --- a/contract/include/evm_runtime/evm_contract.hpp +++ b/contract/include/evm_runtime/evm_contract.hpp @@ -89,7 +89,7 @@ class [[eosio::contract]] evm_contract : public contract [[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 asset& delta); + [[eosio::action]] void addopenbal(name account, const bytes& delta, bool subtract); [[eosio::action]] void freezeaccnt(uint64_t id, bool value); #endif diff --git a/contract/src/admin_actions.cpp b/contract/src/admin_actions.cpp index 459b6b27..5152e181 100644 --- a/contract/src/admin_actions.cpp +++ b/contract/src/admin_actions.cpp @@ -89,17 +89,20 @@ namespace evm_runtime { }); } -[[eosio::action]] void evm_contract::addopenbal(name account, const asset& delta) { +[[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 res = itr->balance.balance + delta; - eosio::check(res.amount >= 0, "negative final balance"); + auto d = to_uint256(delta); open_balances.modify(*itr, eosio::same_payer, [&](auto& row){ - row.balance.balance = res; + if(subtract) { + row.balance-=d; + } else { + row.balance+=d; + } }); } diff --git a/contract/tests/admin_actions_tests.cpp b/contract/tests/admin_actions_tests.cpp index bcc77d2d..ed89c498 100644 --- a/contract/tests/admin_actions_tests.cpp +++ b/contract/tests/admin_actions_tests.cpp @@ -393,7 +393,7 @@ BOOST_FIXTURE_TEST_CASE(addevmbal_add_tests, admin_action_tester) try { 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, make_asset(-1)); + addopenbal(evm_account_name, minimum_natively_representable, true); check_balances(); @@ -402,24 +402,24 @@ BOOST_FIXTURE_TEST_CASE(addevmbal_add_tests, admin_action_tester) try { 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"); - - BOOST_REQUIRE_EXCEPTION(addopenbal("beto"_n, make_asset(100'0000), "alice"_n), + + 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, make_asset(100'0000)), + 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, make_asset(-100'0001)), - eosio_assert_message_exception, eosio_assert_message_is("negative final balance")); + 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, make_asset(asset::max_amount-99'9999)), - eosio_assert_message_exception, eosio_assert_message_is("addition overflow")); + 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(); - intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(18 - 4)); - - addopenbal("alice"_n, make_asset(-1)); + 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) @@ -430,7 +430,7 @@ BOOST_FIXTURE_TEST_CASE(addopenbal_tests, admin_action_tester) try { check_balances(); - addopenbal("alice"_n, make_asset(1)); + 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) @@ -439,7 +439,7 @@ BOOST_FIXTURE_TEST_CASE(addopenbal_tests, admin_action_tester) try { // 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, make_asset(-1)); + addopenbal(evm_account_name, minimum_natively_representable, true); check_balances(); diff --git a/contract/tests/basic_evm_tester.cpp b/contract/tests/basic_evm_tester.cpp index 87019b97..a3f3dd9d 100644 --- a/contract/tests/basic_evm_tester.cpp +++ b/contract/tests/basic_evm_tester.cpp @@ -350,9 +350,10 @@ transaction_trace_ptr basic_evm_tester::addevmbal(uint64_t id, const intx::uint2 mvo()("id", id)("delta",d)("subtract",subtract)); } -transaction_trace_ptr basic_evm_tester::addopenbal(name account, const asset& delta, name actor) { +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",delta)); + mvo()("account", account)("delta",d)("subtract",subtract)); } void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner) diff --git a/contract/tests/basic_evm_tester.hpp b/contract/tests/basic_evm_tester.hpp index 4851cf1b..c557ace6 100644 --- a/contract/tests/basic_evm_tester.hpp +++ b/contract/tests/basic_evm_tester.hpp @@ -243,7 +243,7 @@ class basic_evm_tester : public testing::validating_tester 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 asset& delta, 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); From 00f113373a2e7ed298c511fac2d0bd5d7c2b8322 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 5 Sep 2023 21:04:19 -0300 Subject: [PATCH 7/7] Remove comments --- contract/tests/basic_evm_tester.cpp | 4 ---- contract/tests/basic_evm_tester.hpp | 3 --- 2 files changed, 7 deletions(-) diff --git a/contract/tests/basic_evm_tester.cpp b/contract/tests/basic_evm_tester.cpp index a3f3dd9d..da07c51f 100644 --- a/contract/tests/basic_evm_tester.cpp +++ b/contract/tests/basic_evm_tester.cpp @@ -61,7 +61,6 @@ namespace fc { namespace raw { template<> inline void unpack( datastream& ds, evm_test::partial_account_table_row& tmp) { try { - dlog("enrtro en partial_account_table_row unpack"); fc::raw::unpack(ds, tmp.id); fc::raw::unpack(ds, tmp.eth_address); fc::raw::unpack(ds, tmp.nonce); @@ -594,8 +593,6 @@ bool basic_evm_tester::scan_account_code(std::function visit return true; } -//TODO: remove when merging -//---begin 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)); } @@ -639,6 +636,5 @@ void basic_evm_tester::check_balances() { throw std::runtime_error("evm_eos_balance != total_in_accounts+total_in_evm_accounts"); } } -//---end } // namespace evm_test diff --git a/contract/tests/basic_evm_tester.hpp b/contract/tests/basic_evm_tester.hpp index c557ace6..f1df30db 100644 --- a/contract/tests/basic_evm_tester.hpp +++ b/contract/tests/basic_evm_tester.hpp @@ -289,13 +289,10 @@ class basic_evm_tester : public testing::validating_tester bool scan_gcstore(std::function visitor) const; bool scan_account_code(std::function visitor) const; - //TODO: remove before merge - //---begin balance_and_dust inevm() const; asset get_eos_balance( const account_name& act ); void scan_balances(std::function visitor) const; void check_balances(); - //---end };