diff --git a/crates/handler/src/pre_execution.rs b/crates/handler/src/pre_execution.rs index cd823e28e5..c1a9aa3816 100644 --- a/crates/handler/src/pre_execution.rs +++ b/crates/handler/src/pre_execution.rs @@ -146,28 +146,31 @@ pub fn validate_against_state_and_deduct_caller< let max_balance_spending = tx.max_balance_spending()?; - let mut new_balance = caller_account.info.balance; - // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. // Transfer will be done inside `*_inner` functions. - if is_balance_check_disabled { - // Make sure the caller's balance is at least the value of the transaction. - new_balance = caller_account.info.balance.max(tx.value()); - } else if max_balance_spending > caller_account.info.balance { + if max_balance_spending > caller_account.info.balance && !is_balance_check_disabled { return Err(InvalidTransaction::LackOfFundForMaxFee { fee: Box::new(max_balance_spending), balance: Box::new(caller_account.info.balance), } .into()); - } else { - let effective_balance_spending = tx - .effective_balance_spending(basefee, blob_price) - .expect("effective balance is always smaller than max balance so it can't overflow"); + } - // subtracting max balance spending with value that is going to be deducted later in the call. - let gas_balance_spending = effective_balance_spending - tx.value(); + let effective_balance_spending = tx + .effective_balance_spending(basefee, blob_price) + .expect("effective balance is always smaller than max balance so it can't overflow"); - new_balance = new_balance.saturating_sub(gas_balance_spending); + // subtracting max balance spending with value that is going to be deducted later in the call. + let gas_balance_spending = effective_balance_spending - tx.value(); + + let mut new_balance = caller_account + .info + .balance + .saturating_sub(gas_balance_spending); + + if is_balance_check_disabled { + // Make sure the caller's balance is at least the value of the transaction. + new_balance = new_balance.max(tx.value()); } let old_balance = caller_account.info.balance; diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index 3f238673c8..8aa4543b61 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -174,11 +174,7 @@ where // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. // Transfer will be done inside `*_inner` functions. - if is_balance_check_disabled { - // Make sure the caller's balance is at least the value of the transaction. - // this is not consensus critical, and it is used in testing. - new_balance = caller_account.info.balance.max(tx.value()); - } else if !is_deposit && max_balance_spending > new_balance { + if !is_deposit && max_balance_spending > new_balance && !is_balance_check_disabled { // skip max balance check for deposit transactions. // this check for deposit was skipped previously in `validate_tx_against_state` function return Err(InvalidTransaction::LackOfFundForMaxFee { @@ -186,23 +182,28 @@ where balance: Box::new(new_balance), } .into()); - } else { - let effective_balance_spending = - tx.effective_balance_spending(basefee, blob_price).expect( - "effective balance is always smaller than max balance so it can't overflow", - ); + } - // subtracting max balance spending with value that is going to be deducted later in the call. - let gas_balance_spending = effective_balance_spending - tx.value(); + let effective_balance_spending = tx + .effective_balance_spending(basefee, blob_price) + .expect("effective balance is always smaller than max balance so it can't overflow"); - // If the transaction is not a deposit transaction, subtract the L1 data fee from the - // caller's balance directly after minting the requested amount of ETH. - // Additionally deduct the operator fee from the caller's account. - // - // In case of deposit additional cost will be zero. - let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); + // subtracting max balance spending with value that is going to be deducted later in the call. + let gas_balance_spending = effective_balance_spending - tx.value(); - new_balance = new_balance.saturating_sub(op_gas_balance_spending); + // If the transaction is not a deposit transaction, subtract the L1 data fee from the + // caller's balance directly after minting the requested amount of ETH. + // Additionally deduct the operator fee from the caller's account. + // + // In case of deposit additional cost will be zero. + let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); + + new_balance = new_balance.saturating_sub(op_gas_balance_spending); + + if is_balance_check_disabled { + // Make sure the caller's balance is at least the value of the transaction. + // this is not consensus critical, and it is used in testing. + new_balance = new_balance.max(tx.value()); } // Touch account so we know it is changed. diff --git a/crates/revm/tests/integration.rs b/crates/revm/tests/integration.rs index 28df41ed3d..4aa41ce0c2 100644 --- a/crates/revm/tests/integration.rs +++ b/crates/revm/tests/integration.rs @@ -223,3 +223,52 @@ fn test_frame_stack_index() { assert_eq!(evm.frame_stack.index(), None); compare_or_save_testdata("test_frame_stack_index.json", result1); } + +#[test] +#[cfg(feature = "optional_balance_check")] +fn test_disable_balance_check() { + use database::BENCH_CALLER_BALANCE; + + const RETURN_CALLER_BALANCE_BYTECODE: &[u8] = &[ + opcode::CALLER, + opcode::BALANCE, + opcode::PUSH1, + 0x00, + opcode::MSTORE, + opcode::PUSH1, + 0x20, + opcode::PUSH1, + 0x00, + opcode::RETURN, + ]; + + let mut evm = Context::mainnet() + .modify_cfg_chained(|cfg| cfg.disable_balance_check = true) + .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( + RETURN_CALLER_BALANCE_BYTECODE.into(), + ))) + .build_mainnet(); + + // Construct tx so that effective cost is more than caller balance. + let gas_price = 1; + let gas_limit = 100_000; + // Make sure value doesn't consume all balance since we want to validate that all effective + // cost is deducted. + let tx_value = BENCH_CALLER_BALANCE - U256::from(1); + + let result = evm + .transact_one( + TxEnv::builder_for_bench() + .gas_price(gas_price) + .gas_limit(gas_limit) + .value(tx_value) + .build_fill(), + ) + .unwrap(); + + assert!(result.is_success()); + + let returned_balance = U256::from_be_slice(result.output().unwrap().as_ref()); + let expected_balance = U256::ZERO; + assert_eq!(returned_balance, expected_balance); +}