Skip to content

Commit 6037fa7

Browse files
authored
Correctly budget gas for refund payment in block to non-EOA recipient (#932)
## 📝 Summary When calculating the payout gas budget of a non-EOA refund recipient, `estimate_payout_gas_limit` used `tx_gas_limit_cap` (EIP-7825: 16,777,216) minus the gas used in the block. This caused bundles with those refunds to fail once the block committed past ~16.7M gas, failing with `EstimatePayoutGas(PayoutTxErr(EvmError(Transaction(CallGasCostMoreThanGasLimit { initial_gas: 21000, gas_limit: 0 }))))` Instead, the payout tx gas_limit needs to be at most the EIP-7825 limit, or whatever is left in the block, whichever is less. <!--- A general summary of your changes --> ## 💡 Motivation and Context <!--- (Optional) Why is this change required? What problem does it solve? Remove this section if not applicable. --> prevent bundles from being dropped due to bad refund tx payout calculation --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable)
1 parent 1daf074 commit 6037fa7

1 file changed

Lines changed: 61 additions & 13 deletions

File tree

crates/rbuilder/src/building/payout_tx.rs

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,13 @@ where
174174
.cfg_env
175175
.tx_gas_limit_cap
176176
.unwrap_or(ctx.evm_env.block_env.gas_limit);
177-
let gas_left = max_tx_gas_limit
178-
.checked_sub(space_used.gas)
179-
.unwrap_or_default();
180-
let estimation = insert_test_payout_tx(to, ctx, state, gas_left)?
177+
let max_payout_gas_limit = max_tx_gas_limit.min(
178+
ctx.evm_env
179+
.block_env
180+
.gas_limit
181+
.saturating_sub(space_used.gas),
182+
);
183+
let estimation = insert_test_payout_tx(to, ctx, state, max_payout_gas_limit)?
181184
.ok_or(EstimatePayoutGasErr::FailedToEstimate)?;
182185

183186
if insert_test_payout_tx(to, ctx, state, estimation)?.is_some() {
@@ -189,7 +192,7 @@ where
189192
}
190193

191194
let mut left = estimation;
192-
let mut right = gas_left;
195+
let mut right = max_payout_gas_limit;
193196

194197
// binary search for perfect gas limit
195198
loop {
@@ -227,8 +230,9 @@ mod tests {
227230
use revm::primitives::hardfork::SpecId;
228231
use std::sync::Arc;
229232

230-
#[test]
231-
fn estimate_payout_tx_gas_limit() {
233+
fn setup(
234+
tx_gas_limit_cap: Option<u64>,
235+
) -> (Address, BlockBuildingContext, BlockState<CachedDB>) {
232236
let signer = Signer::random();
233237
let proposer = Address::random();
234238
let chain_spec = MAINNET.clone();
@@ -261,7 +265,7 @@ mod tests {
261265
block.header.mix_hash = B256::random();
262266
block.header.number = EthereumHardfork::Prague.mainnet_activation_block().unwrap();
263267
block.header.excess_blob_gas = Some(1000);
264-
let ctx = BlockBuildingContext::from_onchain_block(
268+
let mut ctx = BlockBuildingContext::from_onchain_block(
265269
block,
266270
chain_spec,
267271
Some(spec_id),
@@ -273,15 +277,59 @@ mod tests {
273277
false,
274278
U256::ZERO,
275279
);
280+
ctx.evm_env.cfg_env.tx_gas_limit_cap = tx_gas_limit_cap;
281+
276282
let cached = CachedDB::new(
277283
provider_factory.latest().unwrap(),
278284
Arc::new(SharedCachedReads::default()),
279285
);
280-
let mut state = BlockState::new(cached);
286+
let state = BlockState::new(cached);
287+
(proposer, ctx, state)
288+
}
289+
290+
#[test]
291+
fn estimate_payout_tx_gas_limit() {
292+
// Pre Fusaka block: no per-tx cap.
293+
let (proposer, ctx, mut state) = setup(None);
294+
295+
let empty_block = estimate_payout_gas_limit(proposer, &ctx, &mut state, BlockSpace::ZERO);
296+
assert_matches!(empty_block, Ok(_));
297+
assert_eq!(empty_block.unwrap().gas, 21_000);
298+
299+
// Only 10k gas left in the block.
300+
let full_block = estimate_payout_gas_limit(
301+
proposer,
302+
&ctx,
303+
&mut state,
304+
BlockSpace::new(29_990_000, 0, 0),
305+
);
306+
assert_matches!(full_block, Err(_));
307+
308+
// Post Fusaka block: EIP-7825 caps one tx at 16,777,216 gas
309+
let (proposer, ctx, mut state) = setup(Some(16_777_216));
281310

282-
let estimate_result =
283-
estimate_payout_gas_limit(proposer, &ctx, &mut state, BlockSpace::ZERO);
284-
assert_matches!(estimate_result, Ok(_));
285-
assert_eq!(estimate_result.unwrap().gas, 21_000);
311+
let empty_block = estimate_payout_gas_limit(proposer, &ctx, &mut state, BlockSpace::ZERO);
312+
assert_matches!(empty_block, Ok(_));
313+
assert_eq!(empty_block.unwrap().gas, 21_000);
314+
315+
// 20M gas is already used. This is more than the single-transaction limit,
316+
// but 10M of block space is still available.
317+
let past_cap = estimate_payout_gas_limit(
318+
proposer,
319+
&ctx,
320+
&mut state,
321+
BlockSpace::new(20_000_000, 0, 0),
322+
);
323+
assert_matches!(past_cap, Ok(_));
324+
assert_eq!(past_cap.unwrap().gas, 21_000);
325+
326+
// Only 10k gas left in the block.
327+
let full_block = estimate_payout_gas_limit(
328+
proposer,
329+
&ctx,
330+
&mut state,
331+
BlockSpace::new(29_990_000, 0, 0),
332+
);
333+
assert_matches!(full_block, Err(_));
286334
}
287335
}

0 commit comments

Comments
 (0)