From a297b075498715e2a5658520031d184674c245bd Mon Sep 17 00:00:00 2001
From: lupin012 <58134934+lupin012@users.noreply.github.com>
Date: Tue, 11 Mar 2025 18:00:35 +0100
Subject: [PATCH 1/3] align estimategas impl to erigon

---
 silkworm/core/execution/evm.cpp               |   2 +-
 silkworm/core/execution/evm.hpp               |   1 +
 silkworm/core/execution/evm_test.cpp          |   2 +-
 silkworm/core/execution/processor.cpp         |   6 +-
 silkworm/rpc/commands/eth_api.cpp             |  42 +++--
 silkworm/rpc/core/estimate_gas_oracle.cpp     |  68 ++++----
 silkworm/rpc/core/estimate_gas_oracle.hpp     |   2 +-
 .../rpc/core/estimate_gas_oracle_test.cpp     | 165 ++++++++----------
 silkworm/rpc/core/evm_executor.cpp            |  18 +-
 silkworm/rpc/core/evm_executor.hpp            |   2 +
 10 files changed, 158 insertions(+), 150 deletions(-)

diff --git a/silkworm/core/execution/evm.cpp b/silkworm/core/execution/evm.cpp
index 1f312d8f39..ad2d2a8734 100644
--- a/silkworm/core/execution/evm.cpp
+++ b/silkworm/core/execution/evm.cpp
@@ -98,7 +98,7 @@ CallResult EVM::execute(const Transaction& txn, uint64_t gas) noexcept {
 
     const auto gas_left = static_cast<uint64_t>(res.gas_left);
     const auto gas_refund = static_cast<uint64_t>(res.gas_refund);
-    return {ValidationResult::kOk, res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}};
+    return {ValidationResult::kOk, res.status_code, gas_left, gas_refund, std::nullopt, {res.output_data, res.output_size}};
 }
 
 evmc::Result EVM::create(const evmc_message& message) noexcept {
diff --git a/silkworm/core/execution/evm.hpp b/silkworm/core/execution/evm.hpp
index 2f3583de41..54a96182ea 100644
--- a/silkworm/core/execution/evm.hpp
+++ b/silkworm/core/execution/evm.hpp
@@ -44,6 +44,7 @@ struct CallResult {
     evmc_status_code status{EVMC_SUCCESS};
     uint64_t gas_left{0};
     uint64_t gas_refund{0};
+    std::optional<uint64_t> gas_used{0};
     Bytes data;
     std::string error_message;
 };
diff --git a/silkworm/core/execution/evm_test.cpp b/silkworm/core/execution/evm_test.cpp
index 1a170c7aab..11abcb588f 100644
--- a/silkworm/core/execution/evm_test.cpp
+++ b/silkworm/core/execution/evm_test.cpp
@@ -569,7 +569,7 @@ class TestTracer : public EvmTracer {
         execution_end_called_ = true;
         const auto gas_left = static_cast<uint64_t>(res.gas_left);
         const auto gas_refund = static_cast<uint64_t>(res.gas_refund);
-        result_ = {ValidationResult::kOk, res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}};
+        result_ = {ValidationResult::kOk, res.status_code, gas_left, gas_refund, std::nullopt, {res.output_data, res.output_size}};
         if (contract_address_ && !pc_stack_.empty()) {
             const auto pc = pc_stack_.back();
             storage_stack_[pc] =
diff --git a/silkworm/core/execution/processor.cpp b/silkworm/core/execution/processor.cpp
index 053bc488b7..234bafd419 100644
--- a/silkworm/core/execution/processor.cpp
+++ b/silkworm/core/execution/processor.cpp
@@ -303,8 +303,10 @@ CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<st
     uint64_t gas_left{result.gas_left};
     uint64_t gas_used{txn.gas_limit - result.gas_left};
 
+    uint64_t gas_refund = 0;
     if (refund && !evm().bailout) {
-        gas_used = txn.gas_limit - refund_gas(txn, effective_gas_price, result.gas_left, result.gas_refund);
+        gas_refund = refund_gas(txn, effective_gas_price, result.gas_left, result.gas_refund) - result.gas_left;
+        gas_used = txn.gas_limit - (result.gas_left + gas_refund);
         gas_left = txn.gas_limit - gas_used;
     }
 
@@ -321,7 +323,7 @@ CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<st
 
     evm_.remove_tracers();
 
-    return {ValidationResult::kOk, result.status, gas_left, gas_used, result.data, result.error_message};
+    return {ValidationResult::kOk, result.status, gas_left, gas_refund, gas_used, result.data, result.error_message};
 }
 
 void ExecutionProcessor::reset() {
diff --git a/silkworm/rpc/commands/eth_api.cpp b/silkworm/rpc/commands/eth_api.cpp
index ad27098acd..1be948e4a4 100644
--- a/silkworm/rpc/commands/eth_api.cpp
+++ b/silkworm/rpc/commands/eth_api.cpp
@@ -886,11 +886,12 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request
     const auto chain_storage{tx->create_storage()};
     rpc::BlockReader block_reader{*chain_storage, *tx};
 
-    std::optional<BlockNum> block_num_for_gas_limit;
+    std::string block_id;
     if (params.size() >= 2) {
-        const auto block_id = params[1].get<std::string>();
+        block_id = params[1].get<std::string>();
         SILK_DEBUG << "block_id: " << block_id;
-        block_num_for_gas_limit = co_await block_reader.get_block_num(block_id);
+    } else {
+        block_id = kLatestBlockId;
     }
 
     AccountsOverrides accounts_overrides;
@@ -902,19 +903,18 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request
     try {
         tx->set_state_cache_enabled(/*cache_enabled=*/true);  // always at latest block
 
-        const BlockNumOrHash block_num_or_hash{kLatestBlockId};
-
         const auto chain_config = co_await chain_storage->read_chain_config();
-        const auto latest_block_num = co_await block_reader.get_block_num(kLatestBlockId);
-        SILK_DEBUG << "chain_id: " << chain_config.chain_id << ", latest_block_num: " << latest_block_num;
+        const auto req_block_num = co_await block_reader.get_block_num(block_id);
+        SILK_DEBUG << "chain_id: " << chain_config.chain_id << ", block_num: " << req_block_num;
 
-        const auto latest_block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, latest_block_num);
-        if (!latest_block_with_hash) {
-            reply = make_json_error(request, 100, "block not found");
+        const auto block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, req_block_num);
+        if (!block_with_hash) {
+            std::string error_msg = "could not find the block " + std::to_string(req_block_num) + " in cache or db";
+            reply = make_json_error(request, -32000, error_msg);
             co_await tx->close();  // RAII not (yet) available with coroutines
             co_return;
         }
-        const auto latest_block = latest_block_with_hash->block;
+        const auto block = block_with_hash->block;
 
         rpc::BlockHeaderProvider block_header_provider = [&chain_storage](BlockNum block_num) {
             return chain_storage->read_canonical_header(block_num);
@@ -926,7 +926,7 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request
         };
 
         rpc::EstimateGasOracle estimate_gas_oracle{block_header_provider, account_reader, chain_config, workers_, *tx, *chain_storage, accounts_overrides};
-        const auto estimated_gas = co_await estimate_gas_oracle.estimate_gas(call, latest_block, std::nullopt /*latest block */, block_num_for_gas_limit);
+        const auto estimated_gas = co_await estimate_gas_oracle.estimate_gas(call, block, std::nullopt /*latest block */);
 
         reply = make_json_content(request, to_quantity(estimated_gas));
     } catch (const std::invalid_argument&) {
@@ -1138,8 +1138,19 @@ Task<void> EthereumRpcApi::handle_eth_get_storage_at(const nlohmann::json& reque
     co_await tx->close();  // RAII not (yet) available with coroutines
 }
 
+//! The current supported version of the Otterscan API
+static constexpr int kCurrentApiLevel{8};
+
+bool first_time = true;
+silkworm::ChainConfig global_chain_config;
+
 // https://eth.wiki/json-rpc/API#eth_call
 Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::string& reply) {
+#ifdef notdef
+    make_glaze_json_null_content(request, reply);
+    co_return;
+#endif
+
     if (!request.contains("params")) {
         auto error_msg = "missing value for required argument 0";
         SILK_ERROR << error_msg << request.dump();
@@ -1171,7 +1182,10 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s
     try {
         const auto chain_storage{tx->create_storage()};
         rpc::BlockReader block_reader{*chain_storage, *tx};
-        const auto chain_config = co_await chain_storage->read_chain_config();
+        if (first_time) {
+            global_chain_config = co_await chain_storage->read_chain_config();
+            first_time = false;
+        }
         const auto [block_num, is_latest_block] = co_await block_reader.get_block_num(block_id, /*latest_required=*/true);
         tx->set_state_cache_enabled(/*cache_enabled=*/is_latest_block);
 
@@ -1191,7 +1205,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s
         }
 
         const auto execution_result = co_await EVMExecutor::call(
-            chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) {
+            global_chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) {
                 return state_factory.create_state(io_executor, storage, curr_txn_id);
             },
             /* tracers */ {}, /* refund */ true, /* gas_bailout */ false, accounts_overrides);
diff --git a/silkworm/rpc/core/estimate_gas_oracle.cpp b/silkworm/rpc/core/estimate_gas_oracle.cpp
index 9b03378e53..73ceafeebd 100644
--- a/silkworm/rpc/core/estimate_gas_oracle.cpp
+++ b/silkworm/rpc/core/estimate_gas_oracle.cpp
@@ -26,19 +26,19 @@
 
 namespace silkworm::rpc {
 
-Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silkworm::Block& block, std::optional<TxnId> txn_id, std::optional<BlockNum> block_num_for_gas_limit) {
+Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silkworm::Block& block, std::optional<TxnId> txn_id) {
     SILK_DEBUG << "EstimateGasOracle::estimate_gas called";
 
     const auto block_num = block.header.number;
 
     uint64_t hi = 0;
-    uint64_t lo = kTxGas - 1;
+    uint64_t lo;
 
     if (call.gas.value_or(0) >= kTxGas) {
         SILK_DEBUG << "Set gas limit using call args: " << call.gas.value_or(0);
         hi = call.gas.value();
     } else {
-        const auto header = co_await block_header_provider_(block_num_for_gas_limit.value_or(block_num));
+        const auto header = co_await block_header_provider_(block_num);
         if (!header) {
             co_return 0;
         }
@@ -46,6 +46,11 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk
         SILK_DEBUG << "Set gas limit using block: " << header->gas_limit;
     }
 
+    if (hi > kGasCap) {
+        SILK_WARN << "caller gas above allowance, capping: requested " << hi << ", cap " << kGasCap;
+        hi = kGasCap;
+    }
+
     std::optional<intx::uint256> gas_price = call.gas_price;
     if (gas_price && gas_price != 0) {
         evmc::address from = call.from.value_or(evmc::address{0});
@@ -71,14 +76,6 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk
         }
     }
 
-    if (hi > kGasCap) {
-        SILK_WARN << "caller gas above allowance, capping: requested " << hi << ", cap " << kGasCap;
-        hi = kGasCap;
-    }
-    auto cap = hi;
-
-    SILK_DEBUG << "hi: " << hi << ", lo: " << lo << ", cap: " << cap;
-
     auto this_executor = co_await boost::asio::this_coro::executor;
 
     execution::StateFactory state_factory{transaction_};
@@ -88,33 +85,40 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk
 
         ExecutionResult result{evmc_status_code::EVMC_SUCCESS};
         silkworm::Transaction transaction{call.to_transaction()};
+
+        auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
+        EVMExecutor executor{block, config_, workers_, state_overrides};
+        transaction.gas_limit = hi;
+        result = try_execution(executor, transaction);
+        if (!result.success()) {
+            return result;
+        }
+
+        // Assuming a contract can freely run all the instructions, we have
+        // the true amount of gas it wants to consume to execute fully.
+        // We want to ensure that the gas used doesn't fall below this
+        auto true_gas = result.gas_used.value_or(0);
+        uint64_t refund = result.gas_refund.value_or(0);
+        lo = std::min(true_gas + refund - 1, kTxGas - 1);
+
+        SILK_DEBUG << "hi: " << hi << ", lo: " << lo;
+
         while (lo + 1 < hi) {
-            auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
-            EVMExecutor executor{block, config_, workers_, state_overrides};
+            state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
+            EVMExecutor curr_executor{block, config_, workers_, state_overrides};
             auto mid = (hi + lo) / 2;
             transaction.gas_limit = mid;
-            result = try_execution(executor, transaction);
-            if (result.success()) {
-                hi = mid;
-            } else {
-                if (result.pre_check_error_code && result.pre_check_error_code != PreCheckErrorCode::kIntrinsicGasTooLow) {
-                    result.status_code = evmc_status_code::EVMC_SUCCESS;
-                    return result;
-                }
+            result = try_execution(curr_executor, transaction);
+            if (result.pre_check_error_code) {
+                break;
+            }
+            if (!result.success() || result.gas_used.value_or(0) < true_gas) {
                 lo = mid;
+            } else {
+                hi = mid;
             }
         }
-
-        if (hi == cap) {
-            auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
-            EVMExecutor executor{block, config_, workers_, state_overrides};
-            transaction.gas_limit = hi;
-            result = try_execution(executor, transaction);
-            SILK_DEBUG << "HI == cap tested again with " << (result.status_code == evmc_status_code::EVMC_SUCCESS ? "succeed" : "failed");
-        } else if (!result.pre_check_error_code || result.pre_check_error_code == PreCheckErrorCode::kIntrinsicGasTooLow) {
-            result.pre_check_error = std::nullopt;
-            result.status_code = evmc_status_code::EVMC_SUCCESS;
-        }
+        result.status_code = evmc_status_code::EVMC_SUCCESS;
 
         SILK_DEBUG << "EstimateGasOracle::estimate_gas returns " << hi;
 
diff --git a/silkworm/rpc/core/estimate_gas_oracle.hpp b/silkworm/rpc/core/estimate_gas_oracle.hpp
index 43eb7eeda4..23c5d70483 100644
--- a/silkworm/rpc/core/estimate_gas_oracle.hpp
+++ b/silkworm/rpc/core/estimate_gas_oracle.hpp
@@ -92,7 +92,7 @@ class EstimateGasOracle {
     EstimateGasOracle(const EstimateGasOracle&) = delete;
     EstimateGasOracle& operator=(const EstimateGasOracle&) = delete;
 
-    Task<intx::uint256> estimate_gas(const Call& call, const silkworm::Block& latest_block, std::optional<TxnId> txn_id, std::optional<BlockNum> block_num_for_gas_limit = {});
+    Task<intx::uint256> estimate_gas(const Call& call, const silkworm::Block& latest_block, std::optional<TxnId> txn_id);
 
   protected:
     virtual ExecutionResult try_execution(EVMExecutor& executor, const silkworm::Transaction& transaction);
diff --git a/silkworm/rpc/core/estimate_gas_oracle_test.cpp b/silkworm/rpc/core/estimate_gas_oracle_test.cpp
index 072b149e06..d7e2a5874b 100644
--- a/silkworm/rpc/core/estimate_gas_oracle_test.cpp
+++ b/silkworm/rpc/core/estimate_gas_oracle_test.cpp
@@ -102,12 +102,12 @@ TEST_CASE("estimate gas") {
     AccountsOverrides accounts_overrides;
     MockEstimateGasOracle estimate_gas_oracle{block_header_provider, account_reader, config, workers, *tx, storage, accounts_overrides};
 
-    SECTION("Call empty, always fails but success in last step") {
+    SECTION("Call empty, always fails but success in first step") {
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS};
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
             .Times(16)
-            .WillOnce(Return(expect_result_fail))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
@@ -126,47 +126,41 @@ TEST_CASE("estimate gas") {
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == kTxGas * 2);
+        // CHECK(estimate_gas == kTxGas * 2);
+        CHECK(estimate_gas == 0xa40f);
+        // 0xa40f == 42000 (0xa410)
     }
 
     SECTION("Call empty, always succeeds") {
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
-        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(14).WillRepeatedly(Return(expect_result_ok));
+        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(15).WillRepeatedly(Return(expect_result_ok));
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
         CHECK(estimate_gas == kTxGas);
     }
 
-    SECTION("Call empty, alternatively fails and succeeds") {
+    SECTION("Call empty, fails first call") {
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS};
-        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-            .Times(14)
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail))
-            .WillRepeatedly(Return(expect_result_ok));
-        auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
-        const intx::uint256& estimate_gas = result.get();
-
-        CHECK(estimate_gas == 0x88b6);
+        try {
+            EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(1).WillOnce(Return(expect_result_fail));
+            auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
+            result.get();
+            CHECK(false);
+        } catch (const silkworm::rpc::EstimateGasException&) {
+            CHECK(true);
+        } catch (const std::exception&) {
+            CHECK(false);
+        } catch (...) {
+            CHECK(false);
+        }
     }
 
     SECTION("Call empty, alternatively succeeds and fails") {
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS};
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-            .Times(14)
+            .Times(16)
             .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_ok))
@@ -184,7 +178,7 @@ TEST_CASE("estimate gas") {
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == 0x6d5e);
+        CHECK(estimate_gas == 0x88b8);
     }
 
     SECTION("Call empty, alternatively succeeds and fails with intrinsic") {
@@ -193,35 +187,30 @@ TEST_CASE("estimate gas") {
             .pre_check_error = "intrinsic ",
             .pre_check_error_code = PreCheckErrorCode::kIntrinsicGasTooLow};
         ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS};
-        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-            .Times(14)
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail_pre_check))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail_pre_check))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail_pre_check))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail_pre_check))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail_pre_check))
-            .WillOnce(Return(expect_result_ok))
-            .WillOnce(Return(expect_result_fail_pre_check))
-            .WillOnce(Return(expect_result_ok))
-            .WillRepeatedly(Return(expect_result_fail));
-        auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
-        const intx::uint256& estimate_gas = result.get();
-
-        CHECK(estimate_gas == 0x6d5e);
+        try {
+            EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
+                .Times(2)
+                .WillOnce(Return(expect_result_ok))
+                .WillOnce(Return(expect_result_fail_pre_check));
+            auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
+            result.get();
+            CHECK(false);
+        } catch (const silkworm::rpc::EstimateGasException&) {
+            CHECK(true);
+        } catch (const std::exception&) {
+            CHECK(false);
+        } catch (...) {
+            CHECK(false);
+        }
     }
 
-    SECTION("Call with gas, always fails but succes last step") {
+    SECTION("Call with gas, always fails but succes first and last step") {
         call.gas = kTxGas * 4;
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS};
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
             .Times(17)
-            .WillOnce(Return(expect_result_fail))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
@@ -241,14 +230,14 @@ TEST_CASE("estimate gas") {
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == kTxGas * 4);
+        CHECK(estimate_gas == 0x1481f);
     }
 
     SECTION("Call with gas, always succeeds") {
         call.gas = kTxGas * 4;
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-            .Times(15)
+            .Times(16)
             .WillRepeatedly(Return(expect_result_ok));
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
@@ -263,27 +252,26 @@ TEST_CASE("estimate gas") {
         call.gas_price = intx::uint256{10'000};
 
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-            .Times(16)
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
+            .Times(15)
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
-            .WillRepeatedly(Return(expect_result_ok));
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok));
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == kTxGas * 2);
+        CHECK(estimate_gas == 0x546f);
     }
 
     SECTION("Call with gas_price, gas capped") {
@@ -294,23 +282,23 @@ TEST_CASE("estimate gas") {
 
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
             .Times(13)
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillRepeatedly(Return(expect_result_ok));
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == 0x61a8);
+        CHECK(estimate_gas == 0x5847);
     }
 
     SECTION("Call with gas_price and value, gas not capped") {
@@ -322,8 +310,8 @@ TEST_CASE("estimate gas") {
 
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
             .Times(16)
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
@@ -341,7 +329,7 @@ TEST_CASE("estimate gas") {
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == kTxGas * 2);
+        CHECK(estimate_gas == 0x7b0a);
     }
 
     SECTION("Call with gas_price and value, gas capped") {
@@ -353,8 +341,8 @@ TEST_CASE("estimate gas") {
 
         EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
             .Times(13)
-            .WillOnce(Return(expect_result_fail))
-            .WillOnce(Return(expect_result_fail))
+            .WillOnce(Return(expect_result_ok))
+            .WillOnce(Return(expect_result_ok))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
             .WillOnce(Return(expect_result_fail))
@@ -369,13 +357,13 @@ TEST_CASE("estimate gas") {
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
-        CHECK(estimate_gas == 0x61a8);
+        CHECK(estimate_gas == 0x59d6);
     }
 
     SECTION("Call gas above allowance, always succeeds, gas capped") {
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         call.gas = kGasCap * 2;
-        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(25).WillRepeatedly(Return(expect_result_ok));
+        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(26).WillRepeatedly(Return(expect_result_ok));
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
@@ -386,7 +374,7 @@ TEST_CASE("estimate gas") {
         ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS};
         call.gas = kTxGas / 2;
 
-        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(14).WillRepeatedly(Return(expect_result_ok));
+        EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(15).WillRepeatedly(Return(expect_result_ok));
         auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
         const intx::uint256& estimate_gas = result.get();
 
@@ -398,7 +386,7 @@ TEST_CASE("estimate gas") {
         call.value = intx::uint256{2'000'000'000};
 
         try {
-            EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(16).WillRepeatedly(Return(expect_result_fail));
+            EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(1).WillRepeatedly(Return(expect_result_fail));
             auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
             result.get();
             CHECK(false);
@@ -422,9 +410,8 @@ TEST_CASE("estimate gas") {
 
         try {
             EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-                .Times(2)
-                .WillOnce(Return(expect_result_fail))
-                .WillRepeatedly(Return(expect_result_fail_pre_check));
+                .Times(1)
+                .WillOnce(Return(expect_result_fail));
             auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
             result.get();
             CHECK(false);
@@ -449,9 +436,8 @@ TEST_CASE("estimate gas") {
 
         try {
             EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-                .Times(2)
-                .WillOnce(Return(expect_result_fail))
-                .WillRepeatedly(Return(expect_result_fail_pre_check));
+                .Times(1)
+                .WillOnce(Return(expect_result_fail));
             auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
             result.get();
             CHECK(false);
@@ -475,9 +461,8 @@ TEST_CASE("estimate gas") {
 
         try {
             EXPECT_CALL(estimate_gas_oracle, try_execution(_, _))
-                .Times(2)
-                .WillOnce(Return(expect_result_fail))
-                .WillRepeatedly(Return(expect_result_fail_pre_check));
+                .Times(1)
+                .WillOnce(Return(expect_result_fail));
             auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future);
             result.get();
             CHECK(false);
diff --git a/silkworm/rpc/core/evm_executor.cpp b/silkworm/rpc/core/evm_executor.cpp
index 08b60d96d2..4e54bf67b2 100644
--- a/silkworm/rpc/core/evm_executor.cpp
+++ b/silkworm/rpc/core/evm_executor.cpp
@@ -176,25 +176,25 @@ ExecutionResult EVMExecutor::convert_validation_result(const ValidationResult& r
         case ValidationResult::kMaxPriorityFeeGreaterThanMax: {
             std::string error = "tip higher than fee cap: address " + from + ", tip: " + intx::to_string(txn.max_priority_fee_per_gas) + " gasFeeCap: " +
                                 intx::to_string(txn.max_fee_per_gas);
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kTipHigherThanFeeCap};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kTipHigherThanFeeCap};
         }
         case ValidationResult::kMaxFeeLessThanBase: {
             std::string error = "fee cap less than block base fee: address " + from + ", gasFeeCap: " +
                                 intx::to_string(txn.max_fee_per_gas) + " baseFee: " + intx::to_string(*block.header.base_fee_per_gas);
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kFeeCapLessThanBlockFeePerGas};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kFeeCapLessThanBlockFeePerGas};
         }
         case ValidationResult::kIntrinsicGas: {
             const intx::uint128 g0{protocol::intrinsic_gas(txn, evm.revision())};
             std::string error = "intrinsic gas too low: have " + std::to_string(txn.gas_limit) + ", want " + intx::to_string(g0);
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kIntrinsicGasTooLow};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kIntrinsicGasTooLow};
         }
         case ValidationResult::kWrongBlockGas: {
             std::string error = "internal failure: Cancun is active but ExcessBlobGas is nil";
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kInternalError};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kInternalError};
         }
         case ValidationResult::kUnsupportedTransactionType: {
             std::string error = "eip-1559 transactions require london";
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kIsNotLondon};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kIsNotLondon};
         }
         case ValidationResult::kInsufficientFunds: {
             auto owned_funds = execution_processor_.intra_block_state().get_balance(*txn.sender());
@@ -208,11 +208,11 @@ ExecutionResult EVMExecutor::convert_validation_result(const ValidationResult& r
                 maximum_cost = txn.maximum_gas_cost();
             }
             std::string error = "insufficient funds for gas * price + value: address " + from + " have " + intx::to_string(owned_funds) + " want " + intx::to_string(maximum_cost + txn.value);
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kInsufficientFunds};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kInsufficientFunds};
         }
         default: {
             std::string error = "internal failure";
-            return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kInternalError};
+            return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kInternalError};
         }
     }
 }
@@ -232,7 +232,7 @@ ExecutionResult EVMExecutor::call(
     evm.bailout = bailout;
 
     if (!txn.sender()) {
-        return {std::nullopt, txn.gas_limit, Bytes{}, "malformed transaction: cannot recover sender"};
+        return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, Bytes{}, "malformed transaction: cannot recover sender"};
     }
 
     const auto result = execution_processor_.call(txn, tracers, refund);
@@ -240,7 +240,7 @@ ExecutionResult EVMExecutor::call(
         return convert_validation_result(result.validation_result, txn);
     }
 
-    ExecutionResult exec_result{result.status, result.gas_left, result.data};
+    ExecutionResult exec_result{result.status, result.gas_left, result.gas_refund, result.gas_used.value_or(0), result.data};
 
     SILK_DEBUG << "EVMExecutor::call call_result: " << exec_result.error_message() << " #data: " << exec_result.data.size() << " end";
 
diff --git a/silkworm/rpc/core/evm_executor.hpp b/silkworm/rpc/core/evm_executor.hpp
index 28fd61a5c2..4c345cd997 100644
--- a/silkworm/rpc/core/evm_executor.hpp
+++ b/silkworm/rpc/core/evm_executor.hpp
@@ -52,6 +52,8 @@ enum class PreCheckErrorCode {
 struct ExecutionResult {
     std::optional<evmc_status_code> status_code;
     uint64_t gas_left{0};
+    std::optional<uint64_t> gas_refund;
+    std::optional<uint64_t> gas_used;
     Bytes data;
     std::optional<std::string> pre_check_error{std::nullopt};
     std::optional<PreCheckErrorCode> pre_check_error_code{std::nullopt};

From 5ff5a0d1624037503a6b104beb86c724a65504c1 Mon Sep 17 00:00:00 2001
From: lupin012 <58134934+lupin012@users.noreply.github.com>
Date: Tue, 11 Mar 2025 18:24:12 +0100
Subject: [PATCH 2/3] remove test code

---
 silkworm/rpc/commands/eth_api.cpp | 18 ++----------------
 1 file changed, 2 insertions(+), 16 deletions(-)

diff --git a/silkworm/rpc/commands/eth_api.cpp b/silkworm/rpc/commands/eth_api.cpp
index 1be948e4a4..ad5028e420 100644
--- a/silkworm/rpc/commands/eth_api.cpp
+++ b/silkworm/rpc/commands/eth_api.cpp
@@ -1138,19 +1138,8 @@ Task<void> EthereumRpcApi::handle_eth_get_storage_at(const nlohmann::json& reque
     co_await tx->close();  // RAII not (yet) available with coroutines
 }
 
-//! The current supported version of the Otterscan API
-static constexpr int kCurrentApiLevel{8};
-
-bool first_time = true;
-silkworm::ChainConfig global_chain_config;
-
 // https://eth.wiki/json-rpc/API#eth_call
 Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::string& reply) {
-#ifdef notdef
-    make_glaze_json_null_content(request, reply);
-    co_return;
-#endif
-
     if (!request.contains("params")) {
         auto error_msg = "missing value for required argument 0";
         SILK_ERROR << error_msg << request.dump();
@@ -1182,10 +1171,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s
     try {
         const auto chain_storage{tx->create_storage()};
         rpc::BlockReader block_reader{*chain_storage, *tx};
-        if (first_time) {
-            global_chain_config = co_await chain_storage->read_chain_config();
-            first_time = false;
-        }
+        auto chain_config = co_await chain_storage->read_chain_config();
         const auto [block_num, is_latest_block] = co_await block_reader.get_block_num(block_id, /*latest_required=*/true);
         tx->set_state_cache_enabled(/*cache_enabled=*/is_latest_block);
 
@@ -1205,7 +1191,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s
         }
 
         const auto execution_result = co_await EVMExecutor::call(
-            global_chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) {
+            chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) {
                 return state_factory.create_state(io_executor, storage, curr_txn_id);
             },
             /* tracers */ {}, /* refund */ true, /* gas_bailout */ false, accounts_overrides);

From 625558206f77896197e3369fd175239c57b750db Mon Sep 17 00:00:00 2001
From: lupin012 <58134934+lupin012@users.noreply.github.com>
Date: Tue, 11 Mar 2025 18:26:26 +0100
Subject: [PATCH 3/3] add const

---
 silkworm/rpc/commands/eth_api.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/silkworm/rpc/commands/eth_api.cpp b/silkworm/rpc/commands/eth_api.cpp
index ad5028e420..5d162990d9 100644
--- a/silkworm/rpc/commands/eth_api.cpp
+++ b/silkworm/rpc/commands/eth_api.cpp
@@ -1171,7 +1171,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s
     try {
         const auto chain_storage{tx->create_storage()};
         rpc::BlockReader block_reader{*chain_storage, *tx};
-        auto chain_config = co_await chain_storage->read_chain_config();
+        const auto chain_config = co_await chain_storage->read_chain_config();
         const auto [block_num, is_latest_block] = co_await block_reader.get_block_num(block_id, /*latest_required=*/true);
         tx->set_state_cache_enabled(/*cache_enabled=*/is_latest_block);