@@ -7,15 +7,17 @@ use crate::{
77 provider:: RootHasher ,
88 roothash:: RootHashError ,
99 utils:: {
10- a2r_withdrawal, default_cfg_env, elapsed_ms,
10+ a2r_withdrawal,
11+ constants:: BASE_TX_GAS ,
12+ default_cfg_env, elapsed_ms,
1113 receipts:: {
1214 calculate_receipt_root_and_block_logs_bloom, calculate_transactions_root, BloomCache ,
1315 TransactionRootCache ,
1416 } ,
1517 timestamp_as_u64, Signer ,
1618 } ,
1719} ;
18- use alloy_consensus:: { Header , EMPTY_OMMER_ROOT_HASH } ;
20+ use alloy_consensus:: { constants :: KECCAK_EMPTY , Header , EMPTY_OMMER_ROOT_HASH } ;
1921use alloy_eips:: {
2022 eip1559:: { calculate_block_gas_limit, ETHEREUM_BLOCK_GAS_LIMIT_30M } ,
2123 eip4844:: BlobTransactionSidecar ,
@@ -52,7 +54,7 @@ use revm::{
5254} ;
5355use serde:: Deserialize ;
5456use std:: {
55- collections:: HashMap ,
57+ collections:: { hash_map , HashMap } ,
5658 hash:: Hash ,
5759 str:: FromStr ,
5860 sync:: Arc ,
@@ -427,6 +429,8 @@ pub struct PartialBlock<Tracer: SimulationTracer> {
427429 pub coinbase_profit : U256 ,
428430 /// Tx execution info belonging to successfully executed orders.
429431 pub executed_tx_infos : Vec < TransactionExecutionInfo > ,
432+ /// Combined refunds.
433+ pub combined_refunds : HashMap < Address , U256 > ,
430434 pub tracer : Tracer ,
431435}
432436
@@ -450,6 +454,8 @@ pub enum InsertPayoutTxErr {
450454 CriticalCommitError ( #[ from] CriticalCommitOrderError ) ,
451455 #[ error( "Profit too low to insert payout tx" ) ]
452456 ProfitTooLow ,
457+ #[ error( "Combined refund tx reverted" ) ]
458+ CombinedRefundTxReverted ,
453459 #[ error( "Payout tx reverted" ) ]
454460 PayoutTxReverted ,
455461 #[ error( "Signer error: {0}" ) ]
@@ -542,6 +548,7 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
542548 blob_gas_used : self . blob_gas_used ,
543549 coinbase_profit : self . coinbase_profit ,
544550 executed_tx_infos : self . executed_tx_infos ,
551+ combined_refunds : self . combined_refunds ,
545552 tracer,
546553 }
547554 }
@@ -580,6 +587,7 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
580587 self . gas_reserved ,
581588 self . blob_gas_used ,
582589 self . discard_txs ,
590+ & self . combined_refunds ,
583591 ) ?;
584592 let ok_result = match exec_result {
585593 Ok ( ok) => ok,
@@ -603,6 +611,18 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
603611 self . blob_gas_used += ok_result. blob_gas_used ;
604612 self . coinbase_profit += ok_result. coinbase_profit ;
605613 self . executed_tx_infos . extend ( ok_result. tx_infos . clone ( ) ) ;
614+
615+ // Update combined refunds
616+ if let Some ( ( address, refund_value) ) = ok_result. delayed_kickback {
617+ let entry = self . combined_refunds . entry ( address) ;
618+ if matches ! ( entry, hash_map:: Entry :: Vacant ( _) ) {
619+ // This is the first refund for the recipient,
620+ // so we need to reserve the gas for the refund tx.
621+ self . gas_reserved += 21_000 ;
622+ }
623+ * entry. or_default ( ) += refund_value;
624+ }
625+
606626 Ok ( Ok ( ExecutionResult {
607627 coinbase_profit : ok_result. coinbase_profit ,
608628 inplace_sim : inplace_sim_result,
@@ -628,7 +648,7 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
628648
629649 /// Inserts payout tx to ctx.attributes.suggested_fee_recipient (should be called at the end of the block)
630650 /// Returns the paid value (block profit after subtracting the burned basefee of the payout tx)
631- pub fn insert_proposer_payout_tx (
651+ pub fn insert_refunds_and_proposer_payout_tx (
632652 & mut self ,
633653 gas_limit : u64 ,
634654 value : U256 ,
@@ -641,13 +661,53 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
641661 . as_ref ( )
642662 . ok_or ( InsertPayoutTxErr :: NoSigner ) ?;
643663 self . free_reserved_gas ( ) ;
644- let nonce = state
664+ let mut nonce = state
645665 . nonce (
646666 builder_signer. address ,
647667 & ctx. shared_cached_reads ,
648668 & mut local_ctx. cached_reads ,
649669 )
650670 . map_err ( CriticalCommitOrderError :: Reth ) ?;
671+
672+ let mut fork = PartialBlockFork :: new ( state, ctx, local_ctx) . with_tracer ( & mut self . tracer ) ;
673+
674+ for ( refund_recipient, refund_amount) in & self . combined_refunds {
675+ let refund_recipient_code_hash = fork
676+ . state
677+ . code_hash (
678+ * refund_recipient,
679+ & ctx. shared_cached_reads ,
680+ & mut fork. local_ctx . cached_reads ,
681+ )
682+ . map_err ( CriticalCommitOrderError :: Reth ) ?;
683+ if refund_recipient_code_hash != KECCAK_EMPTY {
684+ error ! ( %refund_recipient_code_hash, %refund_recipient, %refund_amount, "Refund recipient has code, skipping refund" ) ;
685+ continue ;
686+ }
687+
688+ let refund_tx = TransactionSignedEcRecoveredWithBlobs :: new_no_blobs ( create_payout_tx (
689+ ctx. chain_spec . as_ref ( ) ,
690+ ctx. evm_env . block_env . basefee ,
691+ builder_signer,
692+ nonce,
693+ * refund_recipient,
694+ BASE_TX_GAS ,
695+ * refund_amount,
696+ ) ?)
697+ . unwrap ( ) ;
698+ let refund_result =
699+ fork. commit_tx ( & refund_tx, self . gas_used , 0 , self . blob_gas_used ) ??;
700+ if !refund_result. tx_info . receipt . success {
701+ return Err ( InsertPayoutTxErr :: CombinedRefundTxReverted ) ;
702+ }
703+
704+ self . gas_used += refund_result. tx_info . gas_used ;
705+ self . blob_gas_used += refund_result. blob_gas_used ;
706+ self . executed_tx_infos . push ( refund_result. tx_info ) ;
707+
708+ nonce += 1 ;
709+ }
710+
651711 let tx = create_payout_tx (
652712 ctx. chain_spec . as_ref ( ) ,
653713 ctx. evm_env . block_env . basefee ,
@@ -659,7 +719,6 @@ impl<Tracer: SimulationTracer> PartialBlock<Tracer> {
659719 ) ?;
660720 // payout tx has no blobs so it's safe to unwrap
661721 let tx = TransactionSignedEcRecoveredWithBlobs :: new_no_blobs ( tx) . unwrap ( ) ;
662- let mut fork = PartialBlockFork :: new ( state, ctx, local_ctx) . with_tracer ( & mut self . tracer ) ;
663722 let exec_result = fork. commit_tx ( & tx, self . gas_used , 0 , self . blob_gas_used ) ?;
664723 let ok_result = exec_result?;
665724 if !ok_result. tx_info . receipt . success {
@@ -919,6 +978,7 @@ impl PartialBlock<()> {
919978 blob_gas_used : 0 ,
920979 coinbase_profit : U256 :: ZERO ,
921980 executed_tx_infos : Vec :: new ( ) ,
981+ combined_refunds : HashMap :: default ( ) ,
922982 tracer : ( ) ,
923983 }
924984 }
@@ -1014,6 +1074,7 @@ mod test {
10141074 coinbase_profit: profit_2,
10151075 } ,
10161076 ] ,
1077+ delayed_kickback : None ,
10171078 original_order_ids : Default :: default ( ) ,
10181079 nonces_updated : Default :: default ( ) ,
10191080 paid_kickbacks : Default :: default ( ) ,
@@ -1056,6 +1117,7 @@ mod test {
10561117 gas_used: Default :: default ( ) ,
10571118 coinbase_profit: profit,
10581119 } ] ,
1120+ delayed_kickback : None ,
10591121 original_order_ids : Default :: default ( ) ,
10601122 nonces_updated : Default :: default ( ) ,
10611123 paid_kickbacks : Default :: default ( ) ,
0 commit comments