diff --git a/.github/workflows/contract.yml b/.github/workflows/contract.yml index 60dd7328..c46a67ec 100644 --- a/.github/workflows/contract.yml +++ b/.github/workflows/contract.yml @@ -70,7 +70,7 @@ jobs: owner: AntelopeIO repo: spring file: 'antelope-spring-dev.*ubuntu22\.04_amd64.deb' - target: 'main' + target: '1' prereleases: false artifact-name: antelope-spring-dev-ubuntu22-amd64 container-package: antelope-spring-experimental-binaries diff --git a/include/evm_runtime/evm_contract.hpp b/include/evm_runtime/evm_contract.hpp index 23ba801c..2770cf9d 100644 --- a/include/evm_runtime/evm_contract.hpp +++ b/include/evm_runtime/evm_contract.hpp @@ -138,7 +138,7 @@ class [[eosio::contract]] evm_contract : public contract void assert_inited(); void assert_unfrozen(); - silkworm::Receipt execute_tx(const runtime_config& rc, eosio::name miner, silkworm::Block& block, const transaction& tx, silkworm::ExecutionProcessor& ep); + silkworm::Receipt execute_tx(const runtime_config& rc, eosio::name miner, silkworm::Block& block, const transaction& tx, silkworm::ExecutionProcessor& ep, const evmone::gas_parameters& gas_params); void process_filtered_messages(const std::vector& filtered_messages); uint64_t get_and_increment_nonce(const name owner); diff --git a/include/evm_runtime/tables.hpp b/include/evm_runtime/tables.hpp index b7ad581d..f51feecc 100644 --- a/include/evm_runtime/tables.hpp +++ b/include/evm_runtime/tables.hpp @@ -249,6 +249,10 @@ struct [[eosio::table]] [[eosio::contract("evm_contract")]] config2 struct gas_prices_type { uint64_t overhead_price{0}; uint64_t storage_price{0}; + + uint64_t get_base_price()const { + return std::max(overhead_price, storage_price); + } }; using evm_version_type = uint64_t; diff --git a/silkworm b/silkworm index 6599bd27..7f0cfe57 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 6599bd2765503850dcad3e051464bbf1cbb0ef11 +Subproject commit 7f0cfe576c5993e08509ea2eef23b5e9689381fa diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e70ed3d..5bbcfcc7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,7 @@ list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../silkworm/silkworm/core/crypto/ecdsa.c ${CMAKE_CURRENT_SOURCE_DIR}/../silkworm/silkworm/core/crypto/secp256k1n.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../silkworm/silkworm/core/chain/config.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../silkworm/eosevm/refund_v3.cpp ) add_contract( evm_contract evm_runtime ${SOURCES}) diff --git a/src/actions.cpp b/src/actions.cpp index db50ea5b..28a366f3 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -180,7 +180,7 @@ void check_result( ValidationResult r, const Transaction& txn, const char* desc eosio::check( false, std::move(err_msg)); } -Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Block& block, const transaction& txn, silkworm::ExecutionProcessor& ep) { +Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Block& block, const transaction& txn, silkworm::ExecutionProcessor& ep, const evmone::gas_parameters& gas_params) { const auto& tx = txn.get_tx(); balances balance_table(get_self(), get_self().value); @@ -241,20 +241,23 @@ Receipt evm_contract::execute_tx(const runtime_config& rc, eosio::name miner, Bl ValidationResult r = silkworm::protocol::pre_validate_transaction(tx, ep.evm().revision(), ep.evm().config().chain_id, ep.evm().block().header.base_fee_per_gas, ep.evm().block().header.data_gas_price(), - ep.evm().get_eos_evm_version(), ep.evm().get_gas_params()); + ep.evm().get_eos_evm_version(), gas_params); check_result( r, tx, "pre_validate_transaction error" ); r = silkworm::protocol::validate_transaction(tx, ep.state(), ep.available_gas()); check_result( r, tx, "validate_transaction error" ); Receipt receipt; - ep.execute_transaction(tx, receipt); + const auto res = ep.execute_transaction(tx, receipt, gas_params); // Calculate the miner portion of the actual gas fee (if necessary): std::optional gas_fee_miner_portion; if (miner) { + auto version = _config->get_evm_version(); uint64_t tx_gas_used = receipt.cumulative_gas_used; // Only transaction in the "block" so cumulative_gas_used is the tx gas_used. - if(_config->get_evm_version() >= 1) { + if(version >= 3) { + gas_fee_miner_portion.emplace(res.inclusion_fee); + } else if(version >= 1) { eosio::check(ep.evm().block().header.base_fee_per_gas.has_value(), "no base fee"); intx::uint512 gas_fee = intx::uint256(tx_gas_used) * tx.priority_fee_per_gas(ep.evm().block().header.base_fee_per_gas.value()); check(gas_fee < std::numeric_limits::max(), "too much gas"); @@ -344,12 +347,18 @@ void evm_contract::exec(const exec_input& input, const std::optionalget_genesis_time().sec_since_epoch()); Block block; - auto evm_version = _config->get_evm_version(); + std::optional base_fee_per_gas; + auto gas_prices = _config->get_gas_prices(); if (evm_version >= 1) { - base_fee_per_gas = _config->get_gas_price(); + if( evm_version >= 3) { + base_fee_per_gas = gas_prices.get_base_price(); + } else { + base_fee_per_gas = _config->get_gas_price(); + } } + eosevm::prepare_block_header(block.header, bm, get_self().value, bm.timestamp_to_evm_block_num(eosio::current_time_point().time_since_epoch().count()), evm_version, base_fee_per_gas); @@ -368,7 +377,7 @@ void evm_contract::exec(const exec_input& input, const std::optionalget_gas_prices(); if (current_version >= 1) { if( current_version >= 3) { - //base_fee_per_gas = f(gas_prices, min_inclusion_price) + base_fee_per_gas = gas_prices.get_base_price(); } else { base_fee_per_gas = _config->get_gas_price(); } @@ -506,7 +515,8 @@ void evm_contract::process_tx(const runtime_config& rc, eosio::name miner, const check(tx.max_fee_per_gas >= _config->get_gas_price(), "gas price is too low"); } - silkworm::ExecutionProcessor ep{block, engine, state, *found_chain_config->second, gas_params}; + auto gp = silkworm::gas_prices_t{gas_prices.overhead_price, gas_prices.storage_price}; + silkworm::ExecutionProcessor ep{block, engine, state, *found_chain_config->second, gp}; // Filter EVM messages (with data) that are sent to the reserved address // corresponding to the EOS account holding the contract (self) @@ -515,7 +525,7 @@ void evm_contract::process_tx(const runtime_config& rc, eosio::name miner, const return message.recipient == me && message.input_size > 0; }); - auto receipt = execute_tx(rc, miner, block, txn, ep); + auto receipt = execute_tx(rc, miner, block, txn, ep, gas_params); process_filtered_messages(ep.state().filtered_messages()); diff --git a/src/config_wrapper.cpp b/src/config_wrapper.cpp index ae9879e0..b98fe328 100644 --- a/src/config_wrapper.cpp +++ b/src/config_wrapper.cpp @@ -300,7 +300,7 @@ void config_wrapper::update_consensus_parameters(eosio::asset ram_price_mb, uint account_bytes * gas_per_byte, /* gas_newaccount */ contract_fixed_bytes * gas_per_byte, /*gas_txcreate*/ gas_per_byte,/*gas_codedeposit*/ - gas_sset_min + storage_slot_bytes * gas_per_byte /*gas_sset*/ + (get_evm_version() < 3 ? gas_sset_min : 0) + storage_slot_bytes * gas_per_byte /*gas_sset*/ ); if(get_evm_version() >= 1) { @@ -324,7 +324,7 @@ void config_wrapper::update_consensus_parameters2(std::optional gas_tx if (gas_txcreate.has_value()) v.gas_parameter.gas_txcreate = *gas_txcreate; if (gas_codedeposit.has_value()) v.gas_parameter.gas_codedeposit = *gas_codedeposit; if (gas_sset.has_value()) { - eosio::check(*gas_sset >= gas_sset_min, "gas_sset too small"); + eosio::check(get_evm_version() >= 3 || *gas_sset >= gas_sset_min, "gas_sset too small"); v.gas_parameter.gas_sset = *gas_sset; } }, p); diff --git a/src/test_actions.cpp b/src/test_actions.cpp index 6b84c5d4..5f68fe13 100644 --- a/src/test_actions.cpp +++ b/src/test_actions.cpp @@ -32,7 +32,7 @@ using namespace silkworm; .enforce_chain_id = false, .allow_non_self_miner = true }; - execute_tx(rc, eosio::name{}, block, transaction{std::move(tx)}, ep); + execute_tx(rc, eosio::name{}, block, transaction{std::move(tx)}, ep, {}); } engine.finalize(ep.state(), ep.evm().block()); ep.state().write_to_db(ep.evm().block().header.number); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a91da85d..8d1f850c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,6 +33,7 @@ add_eosio_test_executable( unit_test ${CMAKE_SOURCE_DIR}/mapping_tests.cpp ${CMAKE_SOURCE_DIR}/gas_fee_tests.cpp ${CMAKE_SOURCE_DIR}/gas_param_tests.cpp + ${CMAKE_SOURCE_DIR}/gas_prices_tests.cpp ${CMAKE_SOURCE_DIR}/blockhash_tests.cpp ${CMAKE_SOURCE_DIR}/exec_tests.cpp ${CMAKE_SOURCE_DIR}/call_tests.cpp diff --git a/tests/basic_evm_tester.hpp b/tests/basic_evm_tester.hpp index cdf3b80d..b53b1e33 100644 --- a/tests/basic_evm_tester.hpp +++ b/tests/basic_evm_tester.hpp @@ -344,9 +344,9 @@ class evm_validating_tester : public testing::base_tester { signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms), bool no_throw = false )override { auto produce_block_result = _produce_block(skip_time, false, no_throw); auto sb = produce_block_result.block; - auto [best_head, obh] = validating_node->accept_block( sb->calculate_id(), sb ); - EOS_ASSERT(obh, unlinkable_block_exception, "block did not link ${b}", ("b", sb->calculate_id())); - validating_node->apply_blocks( {}, trx_meta_cache_lookup{} ); + auto bhf = validating_node->create_block_handle_future( sb->calculate_id(), sb ); + struct controller::block_report br; + validating_node->push_block(br, bhf.get(), forked_callback_t{}, trx_meta_cache_lookup{} ); return sb; } @@ -362,17 +362,17 @@ class evm_validating_tester : public testing::base_tester { } void validate_push_block(const signed_block_ptr& sb) { - auto [best_head, obh] = validating_node->accept_block( sb->calculate_id(), sb ); - EOS_ASSERT(obh, unlinkable_block_exception, "block did not link ${b}", ("b", sb->calculate_id())); - validating_node->apply_blocks( {}, trx_meta_cache_lookup{} ); + auto bhf = validating_node->create_block_handle_future( sb->calculate_id(), sb ); + struct controller::block_report br; + validating_node->push_block(br, bhf.get(), forked_callback_t{}, trx_meta_cache_lookup{} ); } signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override { unapplied_transactions.add_aborted( control->abort_block() ); auto sb = _produce_block(skip_time, true); - auto [best_head, obh] = validating_node->accept_block( sb->calculate_id(), sb ); - EOS_ASSERT(obh, unlinkable_block_exception, "block did not link ${b}", ("b", sb->calculate_id())); - validating_node->apply_blocks( {}, trx_meta_cache_lookup{} ); + auto bhf = validating_node->create_block_handle_future( sb->calculate_id(), sb ); + struct controller::block_report br; + validating_node->push_block(br, bhf.get(), forked_callback_t{}, trx_meta_cache_lookup{} ); return sb; } diff --git a/tests/gas_prices_tests.cpp b/tests/gas_prices_tests.cpp new file mode 100644 index 00000000..37acafab --- /dev/null +++ b/tests/gas_prices_tests.cpp @@ -0,0 +1,242 @@ +#include "basic_evm_tester.hpp" + +using namespace eosio::testing; +using namespace evm_test; + +#include + +BOOST_AUTO_TEST_SUITE(evm_gas_prices_tests) + +struct gas_prices_evm_tester : basic_evm_tester +{ + evm_eoa faucet_eoa; + + static constexpr name miner_account_name = "alice"_n; + + gas_prices_evm_tester() : + faucet_eoa(evmc::from_hex("a3f1b69da92a0233ce29485d3049a4ace39e8d384bbc2557e3fc60940ce4e954").value()) + { + create_accounts({miner_account_name}); + transfer_token(faucet_account_name, miner_account_name, make_asset(10000'0000)); + } + + void fund_evm_faucet() + { + transfer_token(faucet_account_name, evm_account_name, make_asset(1000'0000), faucet_eoa.address_0x()); + } + + auto send_tx(auto& eoa, silkworm::Transaction& txn, const silkworm::gas_prices_t& gas_prices, const evmone::gas_parameters& gas_params) -> auto { + auto pre = evm_balance(eoa); + eoa.sign(txn); + pushtx(txn); + auto post = evm_balance(eoa); + BOOST_REQUIRE(pre.has_value() && post.has_value()); + auto inclusion_price = static_cast(txn.priority_fee_per_gas(gas_prices.get_base_price())); + auto effective_gas_price = inclusion_price + static_cast(gas_prices.get_base_price()); + auto cost = *pre - *post - txn.value; + auto total_gas_used = cost/effective_gas_price; + auto scaled_gp = evmone::gas_parameters::apply_discount_factor(inclusion_price, gas_prices.get_base_price(), gas_prices.storage_price, gas_params); + return std::make_tuple(cost, inclusion_price, effective_gas_price, total_gas_used, scaled_gp); + } + + auto get_code_len(uint64_t code_id) { + size_t len=0; + scan_account_code([&](const evm_test::account_code& ac) -> bool { + len = ac.code.size(); + return ac.id == code_id; + }); + return len; + } + + auto pad(const std::string& s) { + const size_t l = 64; + if (s.length() >= l) return s; + size_t pl = l - s.length(); + return std::string(pl, '0') + s; + } + + auto validate_final_fee(const auto& res, uint64_t cpu_gas, uint64_t storage_gas, const silkworm::gas_prices_t& gas_prices) { + + auto [cost, inclusion_price, effective_gas_price, total_gas_billed, scaled_gp] = res; + intx::uint256 total_gas_used = cpu_gas+storage_gas; + + intx::uint256 gas_refund = 0; + if( gas_prices.storage_price >= gas_prices.overhead_price ) { + gas_refund = cpu_gas; + gas_refund *= intx::uint256(gas_prices.storage_price-gas_prices.overhead_price); + gas_refund /= intx::uint256(effective_gas_price); + } + + total_gas_used -= gas_refund; + BOOST_REQUIRE(total_gas_used == total_gas_billed); + BOOST_REQUIRE(cost == total_gas_used*effective_gas_price); + } + +}; + +BOOST_FIXTURE_TEST_CASE(gas_param_scale, gas_prices_evm_tester) try { + using silkworm::kGiga; + + intx::uint256 gas_refund{0}; + + uint64_t suggested_gas_price = 150*kGiga; + init(15555, suggested_gas_price); + produce_block(); + + ///////////////////////////////////// + /// change EOS EVM VERSION => 3 /// + ///////////////////////////////////// + setversion(3, evm_account_name); + fund_evm_faucet(); + produce_block(); + produce_block(); + + auto run_gasparams_scale_test = [&](const silkworm::gas_prices_t& gas_prices) { + + fund_evm_faucet(); + produce_block(); + + setgasprices({.overhead_price=gas_prices.overhead_price, .storage_price=gas_prices.storage_price}); + produce_blocks(2*181); + + // Test traces of `handle_evm_transfer` (EVM VERSION=3) + evm_eoa evm1; + const int64_t to_bridge = 1000000; + auto trace = transfer_token("alice"_n, evm_account_name, make_asset(to_bridge), evm1.address_0x()); + + BOOST_REQUIRE(trace->action_traces.size() == 4); + BOOST_REQUIRE(trace->action_traces[0].act.account == token_account_name); + BOOST_REQUIRE(trace->action_traces[0].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[1].act.account == token_account_name); + BOOST_REQUIRE(trace->action_traces[1].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[2].act.account == token_account_name); + BOOST_REQUIRE(trace->action_traces[2].act.name == "transfer"_n); + BOOST_REQUIRE(trace->action_traces[3].act.account == evm_account_name); + BOOST_REQUIRE(trace->action_traces[3].act.name == "evmtx"_n); + + auto event_v3 = get_event_from_trace(trace->action_traces[3].act.data); + BOOST_REQUIRE(event_v3.eos_evm_version == 3); + BOOST_REQUIRE(event_v3.overhead_price == gas_prices.overhead_price); + BOOST_REQUIRE(event_v3.storage_price == gas_prices.storage_price); + + evmone::gas_parameters gas_params{ + /*G_txnewaccount*/ 10000, + /*G_newaccount*/ 25000, + /*G_txcreate*/ 32000, + /*G_codedeposit*/ 200, + /*G_sset*/ 20000 + }; + + setgasparam(gas_params.G_txnewaccount, gas_params.G_newaccount, gas_params.G_txcreate, gas_params.G_codedeposit, gas_params.G_sset, evm_account_name); + produce_blocks(3); + + // ***************************************************** + // TEST G_codedeposit and G_txcreate + // ***************************************************** + + // pragma solidity >=0.8.2 <0.9.0; + // contract Storage { + // uint256 number; + // function store(uint256 num) public { + // number = num; + // } + // function retrieve() public view returns (uint256) { + // return number; + // } + // function transferRandomWei() public { + // address payable randomAddress = payable(address(uint160(uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, block.number)))))); + // randomAddress.transfer(1 wei); + // } + // receive() external payable {} + // } + + + auto deploy_contract_ex = [&](evm_eoa& eoa, const evmc::bytes& bytecode) { + auto pre = evm_balance(eoa); + const auto gas_price = get_config().gas_price; + const auto base_fee_per_gas = gas_prices.get_base_price(); + auto contract_address = deploy_contract(evm1, bytecode); + auto post = evm_balance(eoa); + BOOST_REQUIRE(pre.has_value() && post.has_value()); + auto inclusion_price = std::min(gas_price, gas_price - base_fee_per_gas); + auto effective_gas_price = inclusion_price + base_fee_per_gas; + auto cost = *pre - *post; + auto total_gas_used = cost/effective_gas_price; + auto scaled_gp = evmone::gas_parameters::apply_discount_factor(inclusion_price, gas_prices.get_base_price(), gas_prices.storage_price, gas_params); + return std::make_tuple(std::make_tuple(cost, inclusion_price, effective_gas_price, total_gas_used, scaled_gp), contract_address); + }; + + const std::string contract_bytecode = "608060405234801561001057600080fd5b50610265806100206000396000f3fe6080604052600436106100385760003560e01c80632e64cec11461004457806357756c591461006f5780636057361d146100865761003f565b3661003f57005b600080fd5b34801561005057600080fd5b506100596100af565b6040516100669190610158565b60405180910390f35b34801561007b57600080fd5b506100846100b8565b005b34801561009257600080fd5b506100ad60048036038101906100a891906101a4565b610135565b005b60008054905090565b60004244436040516020016100cf939291906101f2565b6040516020818303038152906040528051906020012060001c90508073ffffffffffffffffffffffffffffffffffffffff166108fc60019081150290604051600060405180830381858888f19350505050158015610131573d6000803e3d6000fd5b5050565b8060008190555050565b6000819050919050565b6101528161013f565b82525050565b600060208201905061016d6000830184610149565b92915050565b600080fd5b6101818161013f565b811461018c57600080fd5b50565b60008135905061019e81610178565b92915050565b6000602082840312156101ba576101b9610173565b5b60006101c88482850161018f565b91505092915050565b6000819050919050565b6101ec6101e78261013f565b6101d1565b82525050565b60006101fe82866101db565b60208201915061020e82856101db565b60208201915061021e82846101db565b60208201915081905094935050505056fea26469706673582212206c1cecefa543d1df1237047de88cf3e3fe2f7a6ed26ffd4ee4f844ef2987845964736f6c634300080d0033"; + auto [res, contract_address] = deploy_contract_ex(evm1, evmc::from_hex(contract_bytecode).value()); + auto contract_account = find_account_by_address(contract_address).value(); + auto code_len = get_code_len(contract_account.code_id.value()); + // total_cpu_gas_consumed: 30945 + validate_final_fee(res, 30945, code_len*std::get<4>(res).G_codedeposit + std::get<4>(res).G_txcreate, gas_prices); + + // ***************************************************** + // TEST G_sset + // ***************************************************** + auto set_value = [&](auto& eoa, const intx::uint256& v) -> auto { + auto txn = generate_tx(contract_address, 0, 1'000'000); + txn.data = evmc::from_hex("6057361d").value(); //sha3(store(uint256)) + txn.data += evmc::from_hex(pad(intx::hex(v))).value(); + return send_tx(eoa, txn, gas_prices, gas_params); + }; + res = set_value(evm1, intx::uint256{77}); + // total_cpu_gas_consumed: 26646 + validate_final_fee(res, 26646, std::get<4>(res).G_sset, gas_prices); //sset - reset + + // ***************************************************** + // TEST G_txnewaccount + // ***************************************************** + auto send_to_new_address = [&](auto& eoa, const intx::uint256& v) -> auto { + evm_eoa new_address; + auto txn = generate_tx(new_address.address, v, 1'000'000); + return send_tx(eoa, txn, gas_prices, gas_params); + }; + res = send_to_new_address(evm1, intx::uint256{1}); + // total_cpu_gas_consumed: 21000 + validate_final_fee(res, 21000, std::get<4>(res).G_txnewaccount, gas_prices); + + // ***************************************************** + // TEST G_newaccount + // ***************************************************** + + // Fund contract with 100Wei + auto txn_fund = generate_tx(contract_address, intx::uint256{100}, 1'000'000); + evm1.sign(txn_fund); + pushtx(txn_fund); + produce_block(); + + auto transfer_random_wei = [&](auto& eoa) -> auto { + auto txn = generate_tx(contract_address, 0, 1'000'000); + txn.data = evmc::from_hex("57756c59").value(); //sha3(transferRandomWei()) + return send_tx(eoa, txn, gas_prices, gas_params); + }; + + res = transfer_random_wei(evm1); + // total_cpu_gas_consumed: 31250 + validate_final_fee(res, 31250, std::get<4>(res).G_newaccount, gas_prices); + }; + + silkworm::gas_prices_t gas_prices1{ + .overhead_price = 80*kGiga, + .storage_price = 70*kGiga + }; + run_gasparams_scale_test(gas_prices1); + + silkworm::gas_prices_t gas_prices2{ + .overhead_price = 30*kGiga, + .storage_price = 50*kGiga + }; + run_gasparams_scale_test(gas_prices2); + + silkworm::gas_prices_t gas_prices3{ + .overhead_price = 100*kGiga, + .storage_price = 100*kGiga + }; + run_gasparams_scale_test(gas_prices3); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/stack_limit_tests.cpp b/tests/stack_limit_tests.cpp index 9f539a78..4fd4afe4 100644 --- a/tests/stack_limit_tests.cpp +++ b/tests/stack_limit_tests.cpp @@ -91,8 +91,8 @@ BOOST_FIXTURE_TEST_CASE(max_limit_external, stack_limit_tester) try { deploy_simple_contract(evm1); - // At least 11 for external calls. We will try every value until it fails just in case. - const int64_t external_limit = 11; + // At least 10 for external calls. We will try every value until it fails just in case. + const int64_t external_limit = 10; int64_t level = 0; try { for (level = 0; level < 256; ++level) {