diff --git a/crates/rbuilder/src/building/mod.rs b/crates/rbuilder/src/building/mod.rs index 84d2c59a7..297aab60e 100644 --- a/crates/rbuilder/src/building/mod.rs +++ b/crates/rbuilder/src/building/mod.rs @@ -70,7 +70,7 @@ use std::{ }; use thiserror::Error; use time::OffsetDateTime; -use tracing::{error, trace}; +use tracing::{error, trace, warn}; use tx_sim_cache::TxExecutionCache; pub mod bid_adjustments; @@ -864,6 +864,31 @@ impl U256 { + value.saturating_add(U256::from(gas_limit).saturating_mul(U256::from(basefee))) +} + #[derive(Debug, thiserror::Error)] pub enum PayoutTxErr { #[error("Reth error: {0}")] @@ -287,4 +298,53 @@ mod tests { assert_matches!(estimate_result, Ok(_)); assert_eq!(estimate_result.unwrap().gas, 21_000); } + + #[test] + fn payout_tx_cost_covers_value_and_gas() { + let basefee = INITIAL_BASE_FEE; + + // A value-less payout still needs BASE_TX_GAS * basefee: the "21k gas" + // floor from flashbots/rbuilder#296. + assert_eq!( + proposer_payout_tx_cost(U256::ZERO, BASE_TX_GAS, basefee), + U256::from(BASE_TX_GAS) * U256::from(basefee), + ); + + // The transferred value is added on top of the gas cost. + let value = U256::from(7_000_000_000u64); + assert_eq!( + proposer_payout_tx_cost(value, BASE_TX_GAS, basefee), + value + U256::from(BASE_TX_GAS) * U256::from(basefee), + ); + } + + #[test] + fn payout_tx_cost_matches_guard_boundary() { + let basefee = INITIAL_BASE_FEE; + let value = U256::from(7_000_000_000u64); + // Independently computed cost (NOT via the function under test) so the + // assertions below depend on proposer_payout_tx_cost's real output. + let exact = value + U256::from(BASE_TX_GAS) * U256::from(basefee); + + // The guard in insert_refunds_and_proposer_payout_tx warns iff + // builder_balance < proposer_payout_tx_cost(...). A balance of exactly + // the cost is sufficient (no warn); one wei less is insufficient. + assert!( + exact >= proposer_payout_tx_cost(value, BASE_TX_GAS, basefee), + "balance equal to the cost is sufficient", + ); + assert!( + exact - U256::from(1) < proposer_payout_tx_cost(value, BASE_TX_GAS, basefee), + "balance one wei below the cost is insufficient", + ); + } + + #[test] + fn payout_tx_cost_saturates() { + // Implausible inputs must saturate rather than panic on overflow. + assert_eq!( + proposer_payout_tx_cost(U256::MAX, u64::MAX, u64::MAX), + U256::MAX, + ); + } }