Repository: lambdaclass/ethrex
Pull request #2436: fix(levm): try fix blockchain tests
Author: login: JereSalo
Labels: name: levm
description: Lambda EVM implementation
Created: 2025-04-10T13:07:57Z | Merged: 2025-04-11T19:55:52Z

**Motivation**
- Fix most blockchain EF Tests with LEVM.

<!-- Why does this pull request exist? What are its goals? -->

**Description**

Every change made here fixes some tests:
- our gas_used is now like REVM's, no more subtracting gas_used - refunded outside the vm to get gas used.
- there was a little issue with the system_address when we made a generic contract call, we were deleting it from cache when in fact we should only delete it if it didn't exist before.
- I also implemented a backup for the coinbase account to restore it's state when making a generic contract call so that it doesn't change. 
- We had differences in `get_state_transitions` with REVM that did not impact the state but reducing these differences solved some tests. It was simply that we were saying that an EOF had code: None, while with REVM we say code: Some(b“”). The error was kind of silly and I could have fixed it outside the `get_state_transitions` but that was the quick fix. However, it is always good to return the same as the other implementation because it enables us to debug better!
- I fixed gas consumption issue in Prague transactions, we had it wrong in LEVM. I find it strange that the STF EFTests do not test this. I refactored the whole gas consumption in LEVM's `finalize_execution()` because it was kinda messy.
- Fix account removal in LEVM: we were removing the receiver account from the cache when reverting a transaction. This made sense when we used the cache just for executing one transaction because we didn't want to modify the receiver, but in any other scenario it was a mistake!
- Fix blob base fee calculation in opcode `blobbasefee`. We were calculating it in Pectra with Cancun values.

Result: all tests pass except one prague 7702 test and one stack overflow test.




Diff:
diff --git a/cmd/ef_tests/blockchain/test_runner.rs b/cmd/ef_tests/blockchain/test_runner.rs
index 2c715719215..2c2659295a0 100644
--- a/cmd/ef_tests/blockchain/test_runner.rs
+++ b/cmd/ef_tests/blockchain/test_runner.rs
@@ -20,7 +20,7 @@ pub async fn run_ef_test(test_key: &str, test: &TestUnit) {
     // Check world_state
     check_prestate_against_db(test_key, test, &store);
 
-    let blockchain = Blockchain::default_with_store(store.clone());
+    let blockchain = Blockchain::new(ethrex_vm::EvmEngine::REVM, store.clone());
     // Execute all blocks in test
     for block_fixture in test.blocks.iter() {
         let expects_exception = block_fixture.expect_exception.is_some();
diff --git a/crates/common/types/requests.rs b/crates/common/types/requests.rs
index 3bcb9dd267a..513eda22ff2 100644
--- a/crates/common/types/requests.rs
+++ b/crates/common/types/requests.rs
@@ -59,7 +59,7 @@ impl RLPDecode for EncodedRequests {
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub enum Requests {
     Deposit(Vec<Deposit>),
     Withdrawal(Vec<u8>),
diff --git a/crates/vm/backends/levm/db.rs b/crates/vm/backends/levm/db.rs
index 5a3c725cb56..822a032235d 100644
--- a/crates/vm/backends/levm/db.rs
+++ b/crates/vm/backends/levm/db.rs
@@ -1,6 +1,7 @@
 use ethrex_common::types::AccountInfo;
 use ethrex_common::U256 as CoreU256;
 use ethrex_common::{Address as CoreAddress, H256 as CoreH256};
+use ethrex_levm::constants::EMPTY_CODE_HASH;
 use ethrex_levm::db::Database as LevmDatabase;
 
 use crate::db::{ExecutionDB, StoreWrapper};
@@ -89,13 +90,18 @@ impl LevmDatabase for ExecutionDB {
         let Some(acc_info) = self.accounts.get(&address) else {
             return Ok(ethrex_levm::AccountInfo::default());
         };
-        let acc_code = self
-            .code
-            .get(&acc_info.code_hash)
-            .ok_or(DatabaseError::Custom(format!(
-                "Could not find account's code hash {}",
-                &acc_info.code_hash
-            )))?;
+
+        let acc_code = if acc_info.code_hash != EMPTY_CODE_HASH {
+            self.code
+                .get(&acc_info.code_hash)
+                .ok_or(DatabaseError::Custom(format!(
+                    "Could not find account's code hash {}",
+                    &acc_info.code_hash
+                )))?
+        } else {
+            &bytes::Bytes::new()
+        };
+
         Ok(ethrex_levm::AccountInfo {
             balance: acc_info.balance,
             bytecode: acc_code.clone(),
diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs
index 90554be9542..53e84367719 100644
--- a/crates/vm/backends/levm/mod.rs
+++ b/crates/vm/backends/levm/mod.rs
@@ -68,9 +68,7 @@ impl LEVM {
             let report =
                 Self::execute_tx(tx, tx_sender, &block.header, db).map_err(EvmError::from)?;
 
-            // Currently, in LEVM, we don't substract refunded gas to used gas, but that can change in the future.
-            let gas_used = report.gas_used - report.gas_refunded;
-            cumulative_gas_used += gas_used;
+            cumulative_gas_used += report.gas_used;
             let receipt = Receipt::new(
                 tx.tx_type(),
                 matches!(report.result.clone(), TxResult::Success),
@@ -196,12 +194,9 @@ impl LEVM {
             }
 
             let new_state_code_hash = code_hash(&new_state_account.info.bytecode);
-            let code = if initial_state_account.bytecode_hash() != new_state_code_hash {
+            if initial_state_account.bytecode_hash() != new_state_code_hash {
                 acc_info_updated = true;
-                Some(new_state_account.info.bytecode.clone())
-            } else {
-                None
-            };
+            }
 
             // 2. Storage has been updated if the current value is different from the one before execution.
             let mut added_storage = HashMap::new();
@@ -213,14 +208,17 @@ impl LEVM {
                 }
             }
 
-            let info = if acc_info_updated {
-                Some(AccountInfo {
-                    code_hash: new_state_code_hash,
-                    balance: new_state_account.info.balance,
-                    nonce: new_state_account.info.nonce,
-                })
+            let (info, code) = if acc_info_updated {
+                (
+                    Some(AccountInfo {
+                        code_hash: new_state_code_hash,
+                        balance: new_state_account.info.balance,
+                        nonce: new_state_account.info.nonce,
+                    }),
+                    Some(new_state_account.info.bytecode.clone()),
+                )
             } else {
-                None
+                (None, None)
             };
 
             let mut removed = !initial_state_account.is_empty() && new_state_account.is_empty();
@@ -406,6 +404,8 @@ pub fn generic_system_contract_levm(
 ) -> Result<ExecutionReport, EvmError> {
     let chain_config = db.store.get_chain_config();
     let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
+    let system_account_backup = db.cache.get(&system_address).cloned();
+    let coinbase_backup = db.cache.get(&block_header.coinbase).cloned();
     let env = Environment {
         origin: system_address,
         gas_limit: 30_000_000,
@@ -435,7 +435,20 @@ pub fn generic_system_contract_levm(
     .map_err(EvmError::from)?;
 
     let report = vm.execute().map_err(EvmError::from)?;
-    db.cache.remove(&system_address);
+
+    if let Some(system_account) = system_account_backup {
+        db.cache.insert(system_address, system_account);
+    } else {
+        // If the system account was not in the cache, we need to remove it
+        db.cache.remove(&system_address);
+    }
+
+    if let Some(coinbase_account) = coinbase_backup {
+        db.cache.insert(block_header.coinbase, coinbase_account);
+    } else {
+        // If the coinbase account was not in the cache, we need to remove it
+        db.cache.remove(&block_header.coinbase);
+    }
 
     Ok(report)
 }
diff --git a/crates/vm/backends/mod.rs b/crates/vm/backends/mod.rs
index 28b0579a2bf..6bbe0ca1563 100644
--- a/crates/vm/backends/mod.rs
+++ b/crates/vm/backends/mod.rs
@@ -6,6 +6,7 @@ use crate::execution_result::ExecutionResult;
 use crate::helpers::{fork_to_spec_id, spec_id, SpecId};
 use crate::ExecutionDB;
 use crate::{db::StoreWrapper, errors::EvmError};
+use derive_more::derive::Debug;
 use ethrex_common::types::requests::Requests;
 use ethrex_common::types::{
     AccessList, Block, BlockHeader, Fork, GenericTransaction, Receipt, Transaction, Withdrawal,
@@ -274,7 +275,7 @@ impl Evm {
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct BlockExecutionResult {
     pub receipts: Vec<Receipt>,
     pub requests: Vec<Requests>,
diff --git a/crates/vm/backends/revm/mod.rs b/crates/vm/backends/revm/mod.rs
index ab4d64d57dc..66fa9928885 100644
--- a/crates/vm/backends/revm/mod.rs
+++ b/crates/vm/backends/revm/mod.rs
@@ -68,6 +68,7 @@ impl REVM {
                 }
             }
         }
+
         let mut receipts = Vec::new();
         let mut cumulative_gas_used = 0;
 
@@ -80,6 +81,7 @@ impl REVM {
                 cumulative_gas_used,
                 result.logs(),
             );
+
             receipts.push(receipt);
         }
 
diff --git a/crates/vm/levm/src/hooks/default_hook.rs b/crates/vm/levm/src/hooks/default_hook.rs
index 1858f5fc23b..4b309cd7bf5 100644
--- a/crates/vm/levm/src/hooks/default_hook.rs
+++ b/crates/vm/levm/src/hooks/default_hook.rs
@@ -2,7 +2,7 @@ use crate::{
     account::Account,
     call_frame::CallFrame,
     constants::*,
-    db::cache::remove_account,
+    db::cache::{insert_account, remove_account},
     errors::{ExecutionReport, InternalError, TxResult, TxValidationError, VMError},
     gas_cost::{self, STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN},
     hooks::hook::Hook,
@@ -64,7 +64,6 @@ impl Hook for DefaultHook {
                 .ok_or(VMError::Internal(InternalError::GasOverflow))?;
 
             let min_gas_limit = max(intrinsic_gas, floor_cost_by_tokens);
-
             if initial_call_frame.gas_limit < min_gas_limit {
                 return Err(VMError::TxValidation(TxValidationError::IntrinsicGasTooLow));
             }
@@ -350,42 +349,57 @@ impl Hook for DefaultHook {
                 // delegation designations is not rolled back.
                 decrease_account_balance(vm.db, receiver_address, initial_call_frame.msg_value)?;
             } else {
-                // We remove the receiver account from the cache, like nothing changed in it's state.
-                remove_account(&mut vm.db.cache, &receiver_address);
+                // If the receiver of the transaction was in the cache before the transaction we restore it's state,
+                // but if it wasn't then we remove the account from cache like nothing happened.
+                if let Some(receiver_account) = vm.cache_backup.get(&receiver_address) {
+                    insert_account(&mut vm.db.cache, receiver_address, receiver_account.clone());
+                } else {
+                    remove_account(&mut vm.db.cache, &receiver_address);
+                }
             }
 
             increase_account_balance(vm.db, sender_address, initial_call_frame.msg_value)?;
         }
 
         // 2. Return unused gas + gas refunds to the sender.
-        let max_gas = vm.env.gas_limit;
-        let mut consumed_gas = report.gas_used;
+
+        // a. Calculate refunded gas
+        let gas_used_without_refunds = report.gas_used;
+
         // [EIP-3529](https://eips.ethereum.org/EIPS/eip-3529)
-        let quotient = if vm.env.config.fork < Fork::London {
+        // "The max refundable proportion of gas was reduced from one half to one fifth by EIP-3529 by Buterin and Swende [2021] in the London release"
+        let refund_quotient = if vm.env.config.fork < Fork::London {
             MAX_REFUND_QUOTIENT_PRE_LONDON
         } else {
             MAX_REFUND_QUOTIENT
         };
-        let mut refunded_gas = report.gas_refunded.min(
-            consumed_gas
-                .checked_div(quotient)
+        let refunded_gas = report.gas_refunded.min(
+            gas_used_without_refunds
+                .checked_div(refund_quotient)
                 .ok_or(VMError::Internal(InternalError::UndefinedState(-1)))?,
         );
-        // "The max refundable proportion of gas was reduced from one half to one fifth by EIP-3529 by Buterin and Swende [2021] in the London release"
-        report.gas_refunded = refunded_gas;
 
-        if vm.env.config.fork >= Fork::Prague {
-            let floor_gas_price = vm.get_floor_gas_price(initial_call_frame)?;
-            let execution_gas_used = consumed_gas.saturating_sub(refunded_gas);
-            if floor_gas_price > execution_gas_used {
-                consumed_gas = floor_gas_price;
-                refunded_gas = 0;
-            }
-        }
+        // b. Calculate actual gas used in the whole transaction. Since Prague there is a base minimum to be consumed.
+        let exec_gas_consumed = gas_used_without_refunds
+            .checked_sub(refunded_gas)
+            .ok_or(VMError::Internal(InternalError::UndefinedState(-2)))?;
 
-        let gas_to_return = max_gas
-            .checked_sub(consumed_gas)
-            .and_then(|gas| gas.checked_add(refunded_gas))
+        let actual_gas_used = if vm.env.config.fork >= Fork::Prague {
+            let minimum_gas_consumed = vm.get_min_gas_used(initial_call_frame)?;
+            exec_gas_consumed.max(minimum_gas_consumed)
+        } else {
+            exec_gas_consumed
+        };
+
+        // c. Update gas used and refunded in the Execution Report.
+        report.gas_used = actual_gas_used;
+        report.gas_refunded = refunded_gas;
+
+        // d. Finally, return unspent gas to the sender.
+        let gas_to_return = vm
+            .env
+            .gas_limit
+            .checked_sub(actual_gas_used)
             .ok_or(VMError::Internal(InternalError::UndefinedState(0)))?;
 
         let wei_return_amount = vm
@@ -399,16 +413,12 @@ impl Hook for DefaultHook {
         // 3. Pay coinbase fee
         let coinbase_address = vm.env.coinbase;
 
-        let gas_to_pay_coinbase = consumed_gas
-            .checked_sub(refunded_gas)
-            .ok_or(VMError::Internal(InternalError::UndefinedState(2)))?;
-
         let priority_fee_per_gas = vm
             .env
             .gas_price
             .checked_sub(vm.env.base_fee_per_gas)
             .ok_or(VMError::GasPriceIsLowerThanBaseFee)?;
-        let coinbase_fee = U256::from(gas_to_pay_coinbase)
+        let coinbase_fee = U256::from(actual_gas_used)
             .checked_mul(priority_fee_per_gas)
             .ok_or(VMError::BalanceOverflow)?;
 
@@ -416,7 +426,7 @@ impl Hook for DefaultHook {
             increase_account_balance(vm.db, coinbase_address, coinbase_fee)?;
         };
 
-        // 4. Destruct addresses in vm.estruct set.
+        // 4. Destruct addresses in vm.selfdestruct set.
         // In Cancun the only addresses destroyed are contracts created in this transaction
         let selfdestruct_set = vm.accrued_substate.selfdestruct_set.clone();
         for address in selfdestruct_set {
diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs
index d4afaa3adec..d7a19a0ccfa 100644
--- a/crates/vm/levm/src/opcode_handlers/block.rs
+++ b/crates/vm/levm/src/opcode_handlers/block.rs
@@ -6,10 +6,7 @@ use crate::{
     utils::*,
     vm::VM,
 };
-use ethrex_common::{
-    types::{Fork, BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BASE_FEE_PER_BLOB_GAS},
-    U256,
-};
+use ethrex_common::{types::Fork, U256};
 
 // Block Information (11)
 // Opcodes: BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE, BLOBHASH, BLOBBASEFEE
@@ -209,17 +206,6 @@ impl<'a> VM<'a> {
         Ok(OpcodeResult::Continue { pc_increment: 1 })
     }
 
-    fn get_blob_gasprice(&mut self) -> Result<U256, VMError> {
-        fake_exponential(
-            MIN_BASE_FEE_PER_BLOB_GAS.into(),
-            // Use unwrap because env should have a Some value in excess_blob_gas attribute
-            self.env.block_excess_blob_gas.ok_or(VMError::Internal(
-                InternalError::ExcessBlobGasShouldNotBeNone,
-            ))?,
-            BLOB_BASE_FEE_UPDATE_FRACTION.into(),
-        )
-    }
-
     // BLOBBASEFEE operation
     pub fn op_blobbasefee(
         &mut self,
@@ -231,46 +217,11 @@ impl<'a> VM<'a> {
         }
         current_call_frame.increase_consumed_gas(gas_cost::BLOBBASEFEE)?;
 
-        let blob_base_fee = self.get_blob_gasprice()?;
+        let blob_base_fee =
+            get_base_fee_per_blob_gas(self.env.block_excess_blob_gas, &self.env.config)?;
 
         current_call_frame.stack.push(blob_base_fee)?;
 
         Ok(OpcodeResult::Continue { pc_increment: 1 })
     }
 }
-
-// Fuction inspired in EIP 4844 helpers. Link: https://eips.ethereum.org/EIPS/eip-4844#helpers
-fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> Result<U256, VMError> {
-    let mut i = U256::one();
-    let mut output = U256::zero();
-    let mut numerator_accum = factor.checked_mul(denominator).ok_or(VMError::Internal(
-        InternalError::ArithmeticOperationOverflow,
-    ))?;
-    while numerator_accum > U256::zero() {
-        output = output
-            .checked_add(numerator_accum)
-            .ok_or(VMError::Internal(
-                InternalError::ArithmeticOperationOverflow,
-            ))?;
-        let mult_numerator = numerator_accum
-            .checked_mul(numerator)
-            .ok_or(VMError::Internal(
-                InternalError::ArithmeticOperationOverflow,
-            ))?;
-        let mult_denominator = denominator.checked_mul(i).ok_or(VMError::Internal(
-            InternalError::ArithmeticOperationOverflow,
-        ))?;
-        numerator_accum =
-            (mult_numerator)
-                .checked_div(mult_denominator)
-                .ok_or(VMError::Internal(
-                    InternalError::ArithmeticOperationDividedByZero,
-                ))?; // Neither denominator or i can be zero
-        i = i.checked_add(U256::one()).ok_or(VMError::Internal(
-            InternalError::ArithmeticOperationOverflow,
-        ))?;
-    }
-    output.checked_div(denominator).ok_or(VMError::Internal(
-        InternalError::ArithmeticOperationDividedByZero,
-    )) // Denominator is a const
-}
diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs
index d46bc2c7b13..a9b9f9abef4 100644
--- a/crates/vm/levm/src/vm.rs
+++ b/crates/vm/levm/src/vm.rs
@@ -371,7 +371,8 @@ impl<'a> VM<'a> {
         matches!(self.tx_kind, TxKind::Create)
     }
 
-    pub fn get_floor_gas_price(&self, initial_call_frame: &CallFrame) -> Result<u64, VMError> {
+    /// Calculates the minimum gas to be consumed in the transaction.
+    pub fn get_min_gas_used(&self, initial_call_frame: &CallFrame) -> Result<u64, VMError> {
         // If the transaction is a CREATE transaction, the calldata is emptied and the bytecode is assigned.
         let calldata = if self.is_create() {
             &initial_call_frame.bytecode
@@ -388,16 +389,16 @@ impl<'a> VM<'a> {
             .checked_div(STANDARD_TOKEN_COST)
             .ok_or(VMError::Internal(InternalError::DivisionError))?;
 
-        // floor_gas_price = TX_BASE_COST + TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata
-        let mut floor_gas_price: u64 = tokens_in_calldata
+        // min_gas_used = TX_BASE_COST + TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata
+        let mut min_gas_used: u64 = tokens_in_calldata
             .checked_mul(TOTAL_COST_FLOOR_PER_TOKEN)
             .ok_or(VMError::Internal(InternalError::GasOverflow))?;
 
-        floor_gas_price = floor_gas_price
+        min_gas_used = min_gas_used
             .checked_add(TX_BASE_COST)
             .ok_or(VMError::Internal(InternalError::GasOverflow))?;
 
-        Ok(floor_gas_price)
+        Ok(min_gas_used)
     }
 
     /// Executes without making changes to the cache.


Reviews:
user: login: JulianVentura
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:12:29Z
user: login: JereSalo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:22:23Z
user: login: tomip01
body: Very good!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:38:14Z
user: login: JereSalo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:59:12Z

Review comments:
diff_hunk: @@ -89,13 +90,18 @@ impl LevmDatabase for ExecutionDB {
         let Some(acc_info) = self.accounts.get(&address) else {
             return Ok(ethrex_levm::AccountInfo::default());
         };
-        let acc_code = self
-            .code
-            .get(&acc_info.code_hash)
-            .ok_or(DatabaseError::Custom(format!(
-                "Could not find account's code hash {}",
-                &acc_info.code_hash
-            )))?;
+
+        let acc_code = if acc_info.code_hash != code_hash(&Bytes::new()) {
path: crates/vm/backends/levm/db.rs
user: login: JulianVentura
body: nit: This might be implemented by storing the empty code hash so we don't have to perform this check, right?
nit 2: If we maintain this implementation, we could have a constant with the empty code hash so we don't have to calculate it each time we access the `get_account_info` function
created_at: 2025-04-11T15:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -89,13 +90,18 @@ impl LevmDatabase for ExecutionDB {
         let Some(acc_info) = self.accounts.get(&address) else {
             return Ok(ethrex_levm::AccountInfo::default());
         };
-        let acc_code = self
-            .code
-            .get(&acc_info.code_hash)
-            .ok_or(DatabaseError::Custom(format!(
-                "Could not find account's code hash {}",
-                &acc_info.code_hash
-            )))?;
+
+        let acc_code = if acc_info.code_hash != code_hash(&Bytes::new()) {
path: crates/vm/backends/levm/db.rs
user: login: JereSalo
body: I implemented the second nit [here](https://github.com/lambdaclass/ethrex/pull/2436/commits/e03487b9a3b666a469404d0e567b6d654af0cfec).

The first nit is true but I preferred not to mess with the ExecutionDB and this was more simple. But it can be implemented that way too!
created_at: 2025-04-11T16:22:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -351,41 +350,55 @@ impl Hook for DefaultHook {
                 decrease_account_balance(vm.db, receiver_address, initial_call_frame.msg_value)?;
             } else {
                 // We remove the receiver account from the cache, like nothing changed in it's state.
-                remove_account(&mut vm.db.cache, &receiver_address);
+                if let Some(receiver_account) = vm.cache_backup.get(&receiver_address) {
+                    insert_account(&mut vm.db.cache, receiver_address, receiver_account.clone());
+                } else {
+                    remove_account(&mut vm.db.cache, &receiver_address);
+                }
path: crates/vm/levm/src/hooks/default_hook.rs
user: login: tomip01
body: I would add/update the comment like the previous cases when we do this changes
created_at: 2025-04-11T16:32:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -351,41 +350,55 @@ impl Hook for DefaultHook {
                 decrease_account_balance(vm.db, receiver_address, initial_call_frame.msg_value)?;
             } else {
                 // We remove the receiver account from the cache, like nothing changed in it's state.
-                remove_account(&mut vm.db.cache, &receiver_address);
+                if let Some(receiver_account) = vm.cache_backup.get(&receiver_address) {
+                    insert_account(&mut vm.db.cache, receiver_address, receiver_account.clone());
+                } else {
+                    remove_account(&mut vm.db.cache, &receiver_address);
+                }
             }
 
             increase_account_balance(vm.db, sender_address, initial_call_frame.msg_value)?;
         }
 
         // 2. Return unused gas + gas refunds to the sender.
-        let max_gas = vm.env.gas_limit;
-        let mut consumed_gas = report.gas_used;
+
+        // a. Calculate refunded gas
+        let gas_used_without_refunds = report.gas_used;
+
         // [EIP-3529](https://eips.ethereum.org/EIPS/eip-3529)
-        let quotient = if vm.env.config.fork < Fork::London {
+        // "The max refundable proportion of gas was reduced from one half to one fifth by EIP-3529 by Buterin and Swende [2021] in the London release"
+        let refund_quotient = if vm.env.config.fork < Fork::London {
             MAX_REFUND_QUOTIENT_PRE_LONDON
         } else {
             MAX_REFUND_QUOTIENT
         };
-        let mut refunded_gas = report.gas_refunded.min(
-            consumed_gas
-                .checked_div(quotient)
+        let refunded_gas = report.gas_refunded.min(
+            gas_used_without_refunds
+                .checked_div(refund_quotient)
                 .ok_or(VMError::Internal(InternalError::UndefinedState(-1)))?,
         );
-        // "The max refundable proportion of gas was reduced from one half to one fifth by EIP-3529 by Buterin and Swende [2021] in the London release"
-        report.gas_refunded = refunded_gas;
 
-        if vm.env.config.fork >= Fork::Prague {
-            let floor_gas_price = vm.get_floor_gas_price(initial_call_frame)?;
-            let execution_gas_used = consumed_gas.saturating_sub(refunded_gas);
-            if floor_gas_price > execution_gas_used {
-                consumed_gas = floor_gas_price;
-                refunded_gas = 0;
-            }
-        }
+        // b. Calculate actual gas used in the whole transaction. Since Prague there is a base minimum to be consumed.
+        let exec_gas_consumed = gas_used_without_refunds
+            .checked_sub(refunded_gas)
+            .ok_or(VMError::Internal(InternalError::UndefinedState(-2)))?;
 
-        let gas_to_return = max_gas
-            .checked_sub(consumed_gas)
-            .and_then(|gas| gas.checked_add(refunded_gas))
+        let actual_gas_used = if vm.env.config.fork >= Fork::Prague {
+            let minimum_gas_consumed = vm.get_min_gas_used(initial_call_frame)?;
+            exec_gas_consumed.max(minimum_gas_consumed)
+        } else {
+            exec_gas_consumed
+        };
+
+        // c. Update gas used and refunded in the Execution Report.
+        report.gas_used = actual_gas_used;
+        report.gas_refunded = refunded_gas;
+
+        // d. Finally, return unspent gas to the sender.
+        let gas_to_return = vm
+            .env
+            .gas_limit
+            .checked_sub(actual_gas_used)
path: crates/vm/levm/src/hooks/default_hook.rs
user: login: tomip01
body: Nice, much clearer
created_at: 2025-04-11T16:35:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -351,41 +350,55 @@ impl Hook for DefaultHook {
                 decrease_account_balance(vm.db, receiver_address, initial_call_frame.msg_value)?;
             } else {
                 // We remove the receiver account from the cache, like nothing changed in it's state.
-                remove_account(&mut vm.db.cache, &receiver_address);
+                if let Some(receiver_account) = vm.cache_backup.get(&receiver_address) {
+                    insert_account(&mut vm.db.cache, receiver_address, receiver_account.clone());
+                } else {
+                    remove_account(&mut vm.db.cache, &receiver_address);
+                }
path: crates/vm/levm/src/hooks/default_hook.rs
user: login: JereSalo
body: You are right! Thanks for the suggestion!
https://github.com/lambdaclass/ethrex/pull/2436/commits/7c2f23352e9d391725437889524c2281db7e81b4
created_at: 2025-04-11T16:59:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-10T13:09:05Z
body: <h2>Lines of code report</h2>

Total lines added: `18`
Total lines removed: `45`
Total lines changed: `63`

<details>
<summary>Detailed view</summary>

```
+----------------------------------------------------+-------+------+
| File                                               | Lines | Diff |
+----------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/db.rs               | 134   | +4   |
+----------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs              | 491   | +10  |
+----------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/mod.rs                   | 236   | +1   |
+----------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs    | 292   | +3   |
+----------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/block.rs | 161   | -45  |
+----------------------------------------------------+-------+------+

```
</details>

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-10T13:19:54Z
body: # Benchmark Results Comparison
## PR Results
#### Benchmark Results: Factorial
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_Factorial` | 238.5 ± 0.8 | 237.6 | 240.3 | 1.00 |
| `levm_Factorial` | 800.0 ± 10.3 | 789.1 | 824.8 | 3.35 ± 0.04 |
#### Benchmark Results: Factorial - Recursive
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `revm_FactorialRecursive` | 1.470 ± 0.068 | 1.376 | 1.575 | 1.00 |
| `levm_FactorialRecursive` | 13.844 ± 0.024 | 13.819 | 13.887 | 9.42 ± 0.44 |
#### Benchmark Results: Fibonacci
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_Fibonacci` | 209.3 ± 2.5 | 205.9 | 212.1 | 1.00 |
| `levm_Fibonacci` | 789.9 ± 13.8 | 777.7 | 814.3 | 3.77 ± 0.08 |
#### Benchmark Results: ManyHashes
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ManyHashes` | 8.8 ± 0.1 | 8.7 | 9.0 | 1.00 |
| `levm_ManyHashes` | 16.7 ± 0.2 | 16.5 | 17.0 | 1.89 ± 0.03 |
#### Benchmark Results: BubbleSort
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `revm_BubbleSort` | 3.265 ± 0.030 | 3.233 | 3.334 | 1.00 |
| `levm_BubbleSort` | 5.687 ± 0.064 | 5.620 | 5.833 | 1.74 ± 0.03 |
#### Benchmark Results: ERC20 - Transfer
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ERC20Transfer` | 252.2 ± 2.3 | 248.7 | 256.4 | 1.00 |
| `levm_ERC20Transfer` | 488.2 ± 3.5 | 482.9 | 493.4 | 1.94 ± 0.02 |
#### Benchmark Results: ERC20 - Mint
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ERC20Mint` | 141.5 ± 0.7 | 140.9 | 142.8 | 1.00 |
| `levm_ERC20Mint` | 315.3 ± 1.7 | 313.9 | 319.3 | 2.23 ± 0.02 |
#### Benchmark Results: ERC20 - Approval
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ERC20Approval` | 1.046 ± 0.012 | 1.034 | 1.075 | 1.00 |
| `levm_ERC20Approval` | 1.865 ± 0.013 | 1.850 | 1.886 | 1.78 ± 0.02 |
## Main Results
#### Benchmark Results: Factorial
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_Factorial` | 239.3 ± 0.5 | 238.5 | 240.2 | 1.00 |
| `levm_Factorial` | 809.4 ± 5.1 | 802.0 | 815.9 | 3.38 ± 0.02 |
#### Benchmark Results: Factorial - Recursive
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `revm_FactorialRecursive` | 1.429 ± 0.069 | 1.348 | 1.563 | 1.00 |
| `levm_FactorialRecursive` | 13.921 ± 0.213 | 13.684 | 14.163 | 9.74 ± 0.50 |
#### Benchmark Results: Fibonacci
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_Fibonacci` | 208.3 ± 0.7 | 206.8 | 209.6 | 1.00 |
| `levm_Fibonacci` | 798.7 ± 10.0 | 786.2 | 815.2 | 3.83 ± 0.05 |
#### Benchmark Results: ManyHashes
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ManyHashes` | 8.7 ± 0.1 | 8.6 | 8.8 | 1.00 |
| `levm_ManyHashes` | 16.4 ± 0.2 | 16.2 | 16.7 | 1.89 ± 0.02 |
#### Benchmark Results: BubbleSort
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `revm_BubbleSort` | 3.267 ± 0.019 | 3.240 | 3.292 | 1.00 |
| `levm_BubbleSort` | 5.677 ± 0.057 | 5.591 | 5.770 | 1.74 ± 0.02 |
#### Benchmark Results: ERC20 - Transfer
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ERC20Transfer` | 252.7 ± 3.0 | 249.2 | 257.6 | 1.00 |
| `levm_ERC20Transfer` | 488.8 ± 6.3 | 481.0 | 502.7 | 1.93 ± 0.03 |
#### Benchmark Results: ERC20 - Mint
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ERC20Mint` | 141.0 ± 0.3 | 140.3 | 141.5 | 1.00 |
| `levm_ERC20Mint` | 319.1 ± 3.2 | 313.7 | 325.1 | 2.26 ± 0.02 |
#### Benchmark Results: ERC20 - Approval
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `revm_ERC20Approval` | 1.049 ± 0.012 | 1.033 | 1.066 | 1.00 |
| `levm_ERC20Approval` | 1.858 ± 0.017 | 1.833 | 1.879 | 1.77 ± 0.03 |

author_association: NONE