diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index 2f84d4be..1c21258b 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -59,6 +59,7 @@ namespace epee storage_entry load_storage_entry(); void read(section& sec); void read(std::string& str); + void read(array_entry &ae); private: struct recursuion_limitation_guard { @@ -114,6 +115,7 @@ namespace epee void throwable_buffer_reader::read(t_pod_type& pod_val) { RECURSION_LIMITATION(); + static_assert(std::is_pod::value, "POD type expected"); read(&pod_val, sizeof(pod_val)); } @@ -277,5 +279,11 @@ namespace epee m_ptr+=len; m_count -= len; } + inline + void throwable_buffer_reader::read(array_entry &ae) + { + RECURSION_LIMITATION(); + CHECK_AND_ASSERT_THROW_MES(false, "Reading array entry is not supported"); + } } } \ No newline at end of file diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 3b323697..29ba98b1 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -28,6 +28,8 @@ #include "parserse_base_utils.h" #include "file_io_utils.h" +#define EPEE_JSON_RECURSION_LIMIT_INTERNAL 100 + namespace epee { namespace serialization @@ -54,9 +56,10 @@ namespace epee ASSERT_MES_AND_THROW("json parse error"); }*/ template - inline void run_handler(typename t_storage::hsection current_section, std::string::const_iterator& sec_buf_begin, std::string::const_iterator buf_end, t_storage& stg) + inline void run_handler(typename t_storage::hsection current_section, std::string::const_iterator& sec_buf_begin, std::string::const_iterator buf_end, t_storage& stg, unsigned int recursion) { - + CHECK_AND_ASSERT_THROW_MES(recursion < EPEE_JSON_RECURSION_LIMIT_INTERNAL, + "Wrong JSON data: recursion limitation (" << EPEE_JSON_RECURSION_LIMIT_INTERNAL << ") exceeded"); std::string::const_iterator sub_element_start; std::string name; typename t_storage::harray h_array = nullptr; @@ -167,7 +170,7 @@ namespace epee //sub section here typename t_storage::hsection new_sec = stg.open_section(name, current_section, true); CHECK_AND_ASSERT_THROW_MES(new_sec, "Failed to insert new section in json: " << std::string(it, buf_end)); - run_handler(new_sec, it, buf_end, stg); + run_handler(new_sec, it, buf_end, stg, recursion + 1); state = match_state_wonder_after_value; }else if(*it == '[') {//array of something @@ -196,7 +199,7 @@ namespace epee typename t_storage::hsection new_sec = nullptr; h_array = stg.insert_first_section(name, new_sec, current_section); CHECK_AND_ASSERT_THROW_MES(h_array&&new_sec, "failed to create new section"); - run_handler(new_sec, it, buf_end, stg); + run_handler(new_sec, it, buf_end, stg, recursion + 1); state = match_state_array_after_value; array_md = array_mode_sections; }else if(*it == '"') @@ -270,7 +273,7 @@ namespace epee typename t_storage::hsection new_sec = NULL; bool res = stg.insert_next_section(h_array, new_sec); CHECK_AND_ASSERT_THROW_MES(res&&new_sec, "failed to insert next section"); - run_handler(new_sec, it, buf_end, stg); + run_handler(new_sec, it, buf_end, stg, recursion + 1); state = match_state_array_after_value; }else CHECK_ISSPACE(); break; @@ -372,7 +375,7 @@ namespace epee std::string::const_iterator sec_buf_begin = buff_json.begin(); try { - run_handler(nullptr, sec_buf_begin, buff_json.end(), stg); + run_handler(nullptr, sec_buf_begin, buff_json.end(), stg, 0); return true; } catch(const std::exception& ex) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index bd59f381..a41c6c35 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3832,6 +3832,9 @@ bool wallet2::balance(std::unordered_map& balances, u balances.clear(); std::unordered_map balances_map; this->balance(balances_map, mined); - std::unordered_map custom_assets_local = m_custom_assets; - for (auto& own_asset : m_own_asset_descriptors) + for (const auto& [asset_id, balance_entry] : balances_map) { - if (m_whitelisted_assets.find(own_asset.first) == m_whitelisted_assets.end()) - { - custom_assets_local[own_asset.first] = own_asset.second; - } - } + asset_descriptor_base asset_info{}; + uint32_t asset_flags = 0; + if (!get_asset_info(asset_id, asset_info, asset_flags)) + continue; - asset_descriptor_base native_asset_info = AUTO_VAL_INIT(native_asset_info); - native_asset_info.full_name = CURRENCY_NAME_SHORT_BASE; - native_asset_info.ticker = CURRENCY_NAME_ABR; - native_asset_info.decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT; - custom_assets_local[currency::native_coin_asset_id] = native_asset_info; + if (!m_use_assets_whitelisting) + asset_flags &= ~aif_whitelisted; - for (const auto& item : balances_map) - { - asset_descriptor_base asset_info = AUTO_VAL_INIT(asset_info); - //check if asset is whitelisted or customly added + if ((asset_flags & (aif_native_coin | aif_custom | aif_whitelisted)) == 0) + continue; - //check if it custom asset - auto it_cust = custom_assets_local.find(item.first); - if (it_cust == custom_assets_local.end()) - { - if (!m_use_assets_whitelisting) - continue; - - auto it_local = m_whitelisted_assets.find(item.first); - if (it_local == m_whitelisted_assets.end()) - { - WLT_LOG_YELLOW("WARNING: unknown asset " << item.first << " found and skipped; it's NOT included in balance", LOG_LEVEL_1); - continue; - } - else - { - asset_info = it_local->second; - } - } - else - { - asset_info = it_cust->second; - custom_assets_local.erase(it_cust); - } - - balances.push_back(wallet_public::asset_balance_entry()); - wallet_public::asset_balance_entry& new_item = balances.back(); - static_cast(new_item) = item.second; - new_item.asset_info.asset_id = item.first; + wallet_public::asset_balance_entry& new_item = balances.emplace_back(); + static_cast(new_item) = balance_entry; + new_item.asset_info.asset_id = asset_id; static_cast(new_item.asset_info) = asset_info; } //manually added assets should be always present, at least as zero balanced items - for (auto& asset : custom_assets_local) + for (const auto& [asset_id, custom_asset_entry] : m_custom_assets) { - balances.push_back(wallet_public::asset_balance_entry()); - wallet_public::asset_balance_entry& new_item = balances.back(); - new_item.asset_info.asset_id = asset.first; - static_cast(new_item.asset_info) = asset.second; + if (std::find_if(balances.begin(), balances.end(), [&](wallet_public::asset_balance_entry& e){ return e.asset_info.asset_id == asset_id; }) != balances.end()) + continue; + wallet_public::asset_balance_entry& new_item = balances.emplace_back(); + new_item.asset_info.asset_id = asset_id; + static_cast(new_item.asset_info) = custom_asset_entry; } return true; @@ -3961,7 +3933,6 @@ bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset { asset_info = it_own->second; asset_flags |= aif_own; - return true; } // whitelisted? @@ -3970,7 +3941,6 @@ bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset { asset_info = it_white->second; asset_flags |= aif_whitelisted; - return true; } // custom asset? @@ -3979,7 +3949,6 @@ bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset { asset_info = it_cust->second; asset_flags |= aif_custom; - return true; } if (ask_daemon_for_unknown) @@ -3987,11 +3956,10 @@ bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset if (daemon_get_asset_info(asset_id, asset_info)) { asset_flags |= aif_unknown; - return true; } } - return false; + return asset_flags != aif_none; } //---------------------------------------------------------------------------------------------------- size_t wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t result_if_not_found /* = 0 */) const diff --git a/tests/core_tests/wallet_rpc_tests.cpp b/tests/core_tests/wallet_rpc_tests.cpp index 7d3739e4..bd4bd7d5 100644 --- a/tests/core_tests/wallet_rpc_tests.cpp +++ b/tests/core_tests/wallet_rpc_tests.cpp @@ -962,7 +962,7 @@ typedef boost::variant public_key_v; template -bool sign_signature_with_keys(const t_response& rsp_with_data, tools::wallet_rpc_server& attached_wallet_rpc, const secret_key_v& signer_v, const public_key_v& verifier) +bool ext_sign_and_send_asset_tx(const t_response& rsp_with_data, tools::wallet_rpc_server& attached_wallet_rpc, const secret_key_v& signer_v, const public_key_v& verifier) { bool r = false; tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request send_signed_req = AUTO_VAL_INIT(send_signed_req); @@ -993,15 +993,15 @@ bool sign_signature_with_keys(const t_response& rsp_with_data, tools::wallet_rpc return false; } - send_signed_req.unsigned_tx = rsp_with_data.data_for_external_signing->unsigned_tx; - send_signed_req.expected_tx_id = rsp_with_data.tx_id; - send_signed_req.finalized_tx = rsp_with_data.data_for_external_signing->finalized_tx; + send_signed_req.unsigned_tx = rsp_with_data.data_for_external_signing->unsigned_tx; + send_signed_req.expected_tx_id = rsp_with_data.tx_id; + send_signed_req.finalized_tx = rsp_with_data.data_for_external_signing->finalized_tx; send_signed_req.unlock_transfers_on_fail = true; tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response send_signed_resp{}; r = invoke_text_json_for_rpc(attached_wallet_rpc, "send_ext_signed_asset_tx", send_signed_req, send_signed_resp); - CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: "); - CHECK_AND_ASSERT_MES(send_signed_resp.status == API_RETURN_CODE_OK, false, "RPC send_ext_signed_asset_tx failed: "); + CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed"); + CHECK_AND_ASSERT_MES(send_signed_resp.status == API_RETURN_CODE_OK, false, "RPC send_ext_signed_asset_tx failed: " << send_signed_resp.status); return true; } @@ -1025,7 +1025,6 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const r = mine_next_pow_blocks_in_playtime(alice_wlt->get_account().get_public_address(), c, 3); r = mine_next_pow_blocks_in_playtime(bob_wlt->get_account().get_public_address(), c, 3); - r = mine_next_pow_blocks_in_playtime(carol_wlt->get_account().get_public_address(), c, 3); //r = mine_next_pow_blocks_in_playtime(custody_wlt->get_account().get_public_address(), c, 3); r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); @@ -1054,7 +1053,10 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const return false; } + const crypto::public_key asset_id = resp.new_asset_id; + r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, 3); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); // core RPC server currency::t_currency_protocol_handler cprotocol(c, NULL); @@ -1104,26 +1106,34 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const return false; } - r = sign_signature_with_keys(emm_resp, alice_wlt_rpc, miner_wlt->get_account().get_keys().spend_secret_key, miner_wlt->get_account().get_keys().account_address.spend_public_key); - CHECK_AND_ASSERT_MES(r, false, "failed to call sign_signature_with_keys"); + r = ext_sign_and_send_asset_tx(emm_resp, alice_wlt_rpc, miner_wlt->get_account().get_keys().spend_secret_key, miner_wlt->get_account().get_keys().account_address.spend_public_key); + CHECK_AND_ASSERT_MES(r, false, "failed to call ext_sign_and_send_asset_tx"); r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, 3); //check bob wallet - tools::wallet_public::COMMAND_ASSETS_WHITELIST_ADD::request wtl_req = AUTO_VAL_INIT(wtl_req); - tools::wallet_public::COMMAND_ASSETS_WHITELIST_ADD::response wtl_resp = AUTO_VAL_INIT(wtl_resp); - wtl_req.asset_id = resp.new_asset_id; tools::wallet_rpc_server bob_wlt_rpc(bob_wlt); - r = invoke_text_json_for_rpc(bob_wlt_rpc, "assets_whitelist_add", wtl_req, wtl_resp); - CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: "); - CHECK_AND_ASSERT_MES(wtl_resp.status == API_RETURN_CODE_OK, false, "RPC status failed"); - bob_wlt->refresh(); - tools::wallet_public::COMMAND_RPC_GET_BALANCE::request balance_req = AUTO_VAL_INIT(balance_req); tools::wallet_public::COMMAND_RPC_GET_BALANCE::response balance_resp = AUTO_VAL_INIT(balance_resp); r = invoke_text_json_for_rpc(bob_wlt_rpc, "getbalance", balance_req, balance_resp); - CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: "); + CHECK_AND_ASSERT_MES(r, false, "RPC getbalance failed"); + r = std::find_if(balance_resp.balances.begin(), balance_resp.balances.end(), [&](tools::wallet_public::asset_balance_entry& e){ return e.asset_info.asset_id == asset_id; }) == balance_resp.balances.end(); + CHECK_AND_ASSERT_MES(r, false, "found asset " << resp.new_asset_id << " which is unexpected"); + + + tools::wallet_public::COMMAND_ASSETS_WHITELIST_ADD::request wtl_req = AUTO_VAL_INIT(wtl_req); + tools::wallet_public::COMMAND_ASSETS_WHITELIST_ADD::response wtl_resp = AUTO_VAL_INIT(wtl_resp); + wtl_req.asset_id = resp.new_asset_id; + r = invoke_text_json_for_rpc(bob_wlt_rpc, "assets_whitelist_add", wtl_req, wtl_resp); + CHECK_AND_ASSERT_MES(r, false, "RPC assets_whitelist_add failed"); + CHECK_AND_ASSERT_MES(wtl_resp.status == API_RETURN_CODE_OK, false, "RPC status failed"); + bob_wlt->refresh(); + + balance_req = AUTO_VAL_INIT(balance_req); + balance_resp = AUTO_VAL_INIT(balance_resp); + r = invoke_text_json_for_rpc(bob_wlt_rpc, "getbalance", balance_req, balance_resp); + CHECK_AND_ASSERT_MES(r, false, "RPC getbalance failed"); bool found_asset = false; @@ -1132,14 +1142,14 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const if (bal.asset_info.asset_id == resp.new_asset_id) { found_asset = true; - CHECK_AND_ASSERT_MES(bal.total == COINS_TO_TRANSFER, false, "Amount is unexpected"); + CHECK_EQ(bal.total, COINS_TO_TRANSFER); } } - CHECK_AND_ASSERT_MES(found_asset, false, "Asset not found "); + CHECK_AND_ASSERT_MES(found_asset, false, "Asset with id " << resp.new_asset_id << " was not found"); - //transfer ownership of the asset to new address + //transfer ownership of the asset to an ETH key //let's change the owner to ecdsa crypto::eth_secret_key eth_sk_2{}; crypto::eth_public_key eth_pk_2{}; @@ -1160,8 +1170,8 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const return false; } - r = sign_signature_with_keys(res_own, alice_wlt_rpc, miner_wlt->get_account().get_keys().spend_secret_key, miner_wlt->get_account().get_keys().account_address.spend_public_key); - CHECK_AND_ASSERT_MES(r, false, "failed to call sign_signature_with_keys"); + r = ext_sign_and_send_asset_tx(res_own, alice_wlt_rpc, miner_wlt->get_account().get_keys().spend_secret_key, miner_wlt->get_account().get_keys().account_address.spend_public_key); + CHECK_AND_ASSERT_MES(r, false, "failed to call ext_sign_and_send_asset_tx"); r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, 3); @@ -1169,15 +1179,11 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const //now make another tx and see of ownership got changed emm_resp = AUTO_VAL_INIT(emm_resp); r = invoke_text_json_for_rpc(alice_wlt_rpc, "emit_asset", emm_req, emm_resp); - CHECK_AND_ASSERT_MES(r, false, "failed to call"); - if (!emm_resp.data_for_external_signing) - { - LOG_ERROR("Missing data_for_external_signing"); - return false; - } + CHECK_AND_ASSERT_MES(r, false, "invoke_text_json_for_rpc failed"); + CHECK_AND_ASSERT_MES(res_own.data_for_external_signing, false, "data_for_external_signing is missing"); - r = sign_signature_with_keys(emm_resp, alice_wlt_rpc, eth_sk_2, eth_pk_2); - CHECK_AND_ASSERT_MES(r, false, "failed to call sign_signature_with_keys"); + r = ext_sign_and_send_asset_tx(emm_resp, alice_wlt_rpc, eth_sk_2, eth_pk_2); + CHECK_AND_ASSERT_MES(r, false, "failed to call ext_sign_and_send_asset_tx"); r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, 3); @@ -1197,6 +1203,66 @@ bool wallet_rpc_thirdparty_custody::c1(currency::core& c, size_t ev_index, const CHECK_AND_ASSERT_MES(found_asset, false, "Asset not found "); + // Transfer ownership to Carol (standard owner pub key) + + tools::wallet_rpc_server carol_wlt_rpc(carol_wlt); + req_own = AUTO_VAL_INIT(req_own); + res_own = AUTO_VAL_INIT(res_own); + req_own.asset_id = asset_id; + req_own.new_owner = carol_acc.get_public_address().spend_public_key; + alice_wlt->refresh(); + r = invoke_text_json_for_rpc(alice_wlt_rpc, "transfer_asset_ownership", req_own, res_own); + CHECK_AND_ASSERT_MES(r, false, "invoke_text_json_for_rpc failed"); + CHECK_AND_ASSERT_MES(res_own.data_for_external_signing, false, "data_for_external_signing is missing"); + // externally sign and send transfer ownership tx + r = ext_sign_and_send_asset_tx(res_own, alice_wlt_rpc, eth_sk_2, eth_pk_2); + CHECK_AND_ASSERT_MES(r, false, "ext_sign_and_send_asset_tx failed"); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "unexpected pool txs count: " << c.get_pool_transactions_count()); + + // Miner still has some asset coins, transfer them to Carol + miner_wlt->refresh(); + tools::wallet_public::COMMAND_RPC_TRANSFER::request miner_tr_req = AUTO_VAL_INIT(miner_tr_req); + tools::wallet_public::COMMAND_RPC_TRANSFER::request miner_tr_res = AUTO_VAL_INIT(miner_tr_res); + miner_tr_req.fee = TESTS_DEFAULT_FEE; + miner_tr_req.destinations.push_back({COINS_TO_TRANSFER * 7ull, carol_acc.get_public_address_str(), asset_id}); + r = invoke_text_json_for_rpc(miner_wlt_rpc, "transfer", miner_tr_req, miner_tr_res); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "unexpected pool txs count: " << c.get_pool_transactions_count()); + // confirm it + CHECK_AND_ASSERT_MES(mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW), false, ""); + // make sure the pool is now empty + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "unexpected pool txs count: " << c.get_pool_transactions_count()); + + // Carol shouldn't have this asset whitelisted and present in balance + carol_wlt->refresh(); + balance_req = AUTO_VAL_INIT(balance_req); + balance_resp = AUTO_VAL_INIT(balance_resp); + r = invoke_text_json_for_rpc(carol_wlt_rpc, "getbalance", balance_req, balance_resp); + CHECK_AND_ASSERT_MES(r, false, "RPC getbalance failed"); + CHECK_EQ(balance_resp.balance, 0); + r = std::find_if(balance_resp.balances.begin(), balance_resp.balances.end(), [&](tools::wallet_public::asset_balance_entry& e){ return e.asset_info.asset_id == asset_id; }) == balance_resp.balances.end(); + CHECK_AND_ASSERT_MES(r, false, "asset was found, which in unexpected"); + // make sure she has asset_id among own assets in spite of that + auto& carol_own_assets = carol_wlt->get_own_assets(); + CHECK_EQ(carol_own_assets.size(), 1); + CHECK_EQ(carol_own_assets.count(asset_id), 1); + + // whitelist and re-check + wtl_req = AUTO_VAL_INIT(wtl_req); + wtl_resp = AUTO_VAL_INIT(wtl_resp); + wtl_req.asset_id = asset_id; + CHECK_AND_ASSERT_MES(invoke_text_json_for_rpc(carol_wlt_rpc, "assets_whitelist_add", wtl_req, wtl_resp), false, ""); + CHECK_AND_ASSERT_MES(wtl_resp.status == API_RETURN_CODE_OK, false, "RPC failed"); + + // now the asset must show up in the balance + balance_req = AUTO_VAL_INIT(balance_req); + balance_resp = AUTO_VAL_INIT(balance_resp); + r = invoke_text_json_for_rpc(carol_wlt_rpc, "getbalance", balance_req, balance_resp); + CHECK_AND_ASSERT_MES(r, false, "RPC getbalance failed"); + CHECK_EQ(balance_resp.balance, 0); // the balance for native coin is still zero + auto it = std::find_if(balance_resp.balances.begin(), balance_resp.balances.end(), [&](tools::wallet_public::asset_balance_entry& e){ return e.asset_info.asset_id == asset_id; }); + CHECK_AND_ASSERT_MES(it != balance_resp.balances.end(), false, "asset was not found, which in unexpected"); + CHECK_EQ(it->total, COINS_TO_TRANSFER * 7); + return true; } diff --git a/tests/unit_tests/epee_levin_protocol_handler_async.cpp b/tests/unit_tests/epee_levin_protocol_handler_async.cpp index fe8260a1..dbc6602a 100644 --- a/tests/unit_tests/epee_levin_protocol_handler_async.cpp +++ b/tests/unit_tests/epee_levin_protocol_handler_async.cpp @@ -12,7 +12,9 @@ #include "net/levin_protocol_handler_async.h" #include "net/net_utils_base.h" #include "unit_tests_utils.h" - +#include "storages/parserse_base_utils.h" +#include "storages/portable_storage_base.h" +#include "storages/portable_storage.h" namespace { struct test_levin_connection_context : public epee::net_utils::connection_context_base @@ -504,3 +506,61 @@ TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_unexpe ASSERT_FALSE(m_conn->m_protocol_handler.handle_recv(m_buf.data(), m_buf.size())); } + +using epee::serialization::portable_storage; +using epee::serialization::array_entry; +using epee::serialization::section; +using epee::serialization::throwable_buffer_reader; + +/** + * Purpose: + * Verify what the deserialization of array_entry no longer uses memcpy to + * overwrite the boost::variant memory directly. Instead, an unsupported-array-entry + * path should throw an exception indicating array_entry deserialization isn't supported. + */ +TEST(levin_protocol_variant_memcpy, memcpy_variant_verify) +{ + std::string buf; // raw buffer simulating an array_entry section + + buf.push_back(static_cast(SERIALIZE_FLAG_ARRAY | SERIALIZE_TYPE_ARRAY)); + buf.push_back(static_cast(1 << 2)); + buf.append(sizeof(array_entry), char(0x41)); + + throwable_buffer_reader reader(reinterpret_cast(buf.data()), buf.size()); + + EXPECT_THROW( + reader.load_storage_array_entry(SERIALIZE_TYPE_ARRAY), + std::runtime_error + ) << "Expected load_storage_array_entry to throw due to array_entry"; +} + +/** + * Purpose: + * Construct a JSON string nested deeper than the built-in recursion limit (100 levels). + */ +TEST(json_parse_deep, parser_deep) +{ + const int depth = 200; + std::string json; + json.reserve(depth * 10); + + // Build a deeply nested JSON + // {"level": {"level": { ... {"level":1} ... }}} + for (int i = 0; i < depth; ++i) + { + json += '{'; + json += "\"level\":"; + } + json += '1'; + + for (int i = 0; i < depth; ++i) + { + json += '}'; + } + + portable_storage storage; + bool ok = epee::serialization::json::load_from_json(json, storage); + + EXPECT_FALSE(ok) << "Expected load_from_json to fail when depth " << depth + << " exceeds the 100-level recursion limit."; +}