1use alloy_primitives::U256;
2use arb_primitives::multigas::{MultiGas, ResourceKind, NUM_RESOURCE_KIND};
3use revm::Database;
4
5use arb_chainspec::arbos_version as version;
6
7use super::L2PricingState;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum GasModel {
12 Unknown,
13 Legacy,
14 SingleGasConstraints,
15 MultiGasConstraints,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum BacklogOperation {
21 Shrink,
22 Grow,
23}
24
25pub const MULTI_CONSTRAINT_STATIC_BACKLOG_UPDATE_COST: u64 = 20_800;
28
29impl<D: Database> L2PricingState<D> {
30 pub fn gas_model_to_use(&self) -> Result<GasModel, ()> {
32 if self.arbos_version >= version::ARBOS_VERSION_60 {
33 let mgc_len = self.multi_gas_constraints_length()?;
34 if mgc_len > 0 {
35 return Ok(GasModel::MultiGasConstraints);
36 }
37 }
38 if self.arbos_version >= version::ARBOS_VERSION_50 {
39 let gc_len = self.gas_constraints_length()?;
40 if gc_len > 0 {
41 return Ok(GasModel::SingleGasConstraints);
42 }
43 }
44 Ok(GasModel::Legacy)
45 }
46
47 pub fn grow_backlog(&self, used_gas: u64, used_multi_gas: MultiGas) -> Result<(), ()> {
49 self.update_backlog(BacklogOperation::Grow, used_gas, used_multi_gas)
50 }
51
52 pub fn shrink_backlog(&self, used_gas: u64, used_multi_gas: MultiGas) -> Result<(), ()> {
54 self.update_backlog(BacklogOperation::Shrink, used_gas, used_multi_gas)
55 }
56
57 fn update_backlog(
59 &self,
60 op: BacklogOperation,
61 used_gas: u64,
62 used_multi_gas: MultiGas,
63 ) -> Result<(), ()> {
64 match self.gas_model_to_use()? {
65 GasModel::Legacy | GasModel::Unknown => self.update_legacy_backlog_op(op, used_gas),
66 GasModel::SingleGasConstraints => {
67 self.update_single_gas_constraints_backlogs_op(op, used_gas)
68 }
69 GasModel::MultiGasConstraints => {
70 self.update_multi_gas_constraints_backlogs_op(op, used_multi_gas)
71 }
72 }
73 }
74
75 fn update_legacy_backlog_op(&self, op: BacklogOperation, gas: u64) -> Result<(), ()> {
76 let backlog = self.gas_backlog()?;
77 let new_backlog = apply_gas_delta_op(op, backlog, gas);
78 self.set_gas_backlog(new_backlog)
79 }
80
81 fn update_single_gas_constraints_backlogs_op(
82 &self,
83 op: BacklogOperation,
84 gas: u64,
85 ) -> Result<(), ()> {
86 let len = self.gas_constraints_length()?;
87 for i in 0..len {
88 let c = self.open_gas_constraint_at(i);
89 let backlog = c.backlog()?;
90 c.set_backlog(apply_gas_delta_op(op, backlog, gas))?;
91 }
92 Ok(())
93 }
94
95 fn update_multi_gas_constraints_backlogs_op(
96 &self,
97 op: BacklogOperation,
98 multi_gas: MultiGas,
99 ) -> Result<(), ()> {
100 let len = self.multi_gas_constraints_length()?;
101 for i in 0..len {
102 let c = self.open_multi_gas_constraint_at(i);
103 match op {
104 BacklogOperation::Grow => c.grow_backlog(multi_gas)?,
105 BacklogOperation::Shrink => c.shrink_backlog(multi_gas)?,
106 }
107 }
108 Ok(())
109 }
110
111 pub fn update_pricing_model(&self, time_passed: u64, arbos_version: u64) -> Result<(), ()> {
113 let _ = arbos_version; match self.gas_model_to_use()? {
115 GasModel::Legacy | GasModel::Unknown => self.update_pricing_model_legacy(time_passed),
116 GasModel::SingleGasConstraints => {
117 self.update_pricing_model_single_constraints(time_passed)
118 }
119 GasModel::MultiGasConstraints => {
120 self.update_pricing_model_multi_constraints(time_passed)
121 }
122 }
123 }
124
125 fn update_pricing_model_legacy(&self, time_passed: u64) -> Result<(), ()> {
126 let speed_limit = self.speed_limit_per_second()?;
127 let drain = time_passed.saturating_mul(speed_limit);
128 self.update_legacy_backlog_op(BacklogOperation::Shrink, drain)?;
129
130 let inertia = self.pricing_inertia()?;
131 let tolerance = self.backlog_tolerance()?;
132 let backlog = self.gas_backlog()?;
133 let min_base_fee = self.min_base_fee_wei()?;
134
135 let tolerance_limit = tolerance.wrapping_mul(speed_limit);
137 let base_fee = if backlog > tolerance_limit {
138 let divisor = saturating_cast_to_i64(inertia.saturating_mul(speed_limit));
142 if divisor == 0 {
143 return self.set_base_fee_wei(min_base_fee);
144 }
145 let excess = saturating_cast_to_i64(backlog.wrapping_sub(tolerance_limit));
147 let exponent_bips = natural_to_bips(excess) / divisor;
149 self.calc_base_fee_from_exponent(exponent_bips.max(0) as u64)?
151 } else {
152 min_base_fee
153 };
154
155 self.set_base_fee_wei(base_fee)
156 }
157
158 fn update_pricing_model_single_constraints(&self, time_passed: u64) -> Result<(), ()> {
159 let mut total_exponent: i64 = 0;
162 let len = self.gas_constraints_length()?;
163
164 for i in 0..len {
165 let c = self.open_gas_constraint_at(i);
166 let target = c.target()?;
167
168 let backlog = c.backlog()?;
170 let gas = time_passed.saturating_mul(target);
171 let new_backlog = backlog.saturating_sub(gas);
172 c.set_backlog(new_backlog)?;
173
174 if new_backlog > 0 {
176 let window = c.adjustment_window()?;
177 let divisor = saturating_cast_to_i64(window.saturating_mul(target));
179 if divisor != 0 {
180 let exponent = natural_to_bips(saturating_cast_to_i64(new_backlog)) / divisor;
182 total_exponent = total_exponent.saturating_add(exponent);
183 }
184 }
185 }
186
187 let base_fee = self.calc_base_fee_from_exponent(total_exponent.max(0) as u64)?;
188 self.set_base_fee_wei(base_fee)
189 }
190
191 fn update_pricing_model_multi_constraints(&self, time_passed: u64) -> Result<(), ()> {
192 self.update_multi_gas_constraints_backlogs(time_passed)?;
193
194 let exponent_per_kind = self.calc_multi_gas_constraints_exponents()?;
195
196 let mut max_base_fee = self.min_base_fee_wei()?;
199 let fees = &self.multi_gas_base_fees;
200
201 for (i, &exp) in exponent_per_kind.iter().enumerate() {
202 let base_fee = self.calc_base_fee_from_exponent(exp)?;
203 if let Some(kind) = ResourceKind::from_u8(i as u8) {
204 let mgf = super::multi_gas_fees::open_multi_gas_fees(fees.clone());
205 mgf.set_next_block_fee(kind, base_fee)?;
206 }
207 if base_fee > max_base_fee {
208 max_base_fee = base_fee;
209 }
210 }
211
212 self.set_base_fee_wei(max_base_fee)
213 }
214
215 fn update_multi_gas_constraints_backlogs(&self, time_passed: u64) -> Result<(), ()> {
216 let len = self.multi_gas_constraints_length()?;
217 for i in 0..len {
218 let c = self.open_multi_gas_constraint_at(i);
219 let target = c.target()?;
220 let backlog = c.backlog()?;
221 let gas = time_passed.saturating_mul(target);
222 let new_backlog = backlog.saturating_sub(gas);
223 c.set_backlog(new_backlog)?;
224 }
225 Ok(())
226 }
227
228 pub fn calc_multi_gas_constraints_exponents(&self) -> Result<[u64; NUM_RESOURCE_KIND], ()> {
238 let len = self.multi_gas_constraints_length()?;
239 let mut exponent_per_kind = [0i64; NUM_RESOURCE_KIND];
240
241 for i in 0..len {
242 let c = self.open_multi_gas_constraint_at(i);
243 let target = c.target()?;
244 let backlog = c.backlog()?;
245
246 if backlog == 0 {
247 continue;
248 }
249
250 let window = c.adjustment_window()?;
251 let max_weight = c.max_weight()?;
252
253 if target == 0 || window == 0 || max_weight == 0 {
254 continue;
255 }
256
257 let divisor_u64 = (window as u64).saturating_mul(target.saturating_mul(max_weight));
260 let divisor = saturating_cast_to_i64(divisor_u64);
261 if divisor == 0 {
262 continue;
263 }
264
265 for kind in ResourceKind::ALL {
266 let weight = c.resource_weight(kind)?;
267 if weight == 0 {
268 continue;
269 }
270
271 let product = backlog.saturating_mul(weight);
274 let cast = saturating_cast_to_i64(product);
275 let dividend = natural_to_bips(cast);
276
277 let exp = dividend / divisor;
278 exponent_per_kind[kind as usize] =
279 exponent_per_kind[kind as usize].saturating_add(exp);
280 }
281 }
282
283 let mut result = [0u64; NUM_RESOURCE_KIND];
285 for i in 0..NUM_RESOURCE_KIND {
286 result[i] = exponent_per_kind[i].max(0) as u64;
287 }
288 Ok(result)
289 }
290
291 pub fn calc_base_fee_from_exponent(&self, exponent_bips: u64) -> Result<U256, ()> {
294 let min_base_fee = self.min_base_fee_wei()?;
295 if exponent_bips == 0 {
296 return Ok(min_base_fee);
297 }
298
299 let exp_result = approx_exp_basis_points(exponent_bips);
300 let base_fee = (min_base_fee * U256::from(exp_result)) / U256::from(10000u64);
301
302 if base_fee < min_base_fee {
303 Ok(min_base_fee)
304 } else {
305 Ok(base_fee)
306 }
307 }
308
309 pub fn get_multi_gas_base_fee_per_resource(&self) -> Result<[U256; NUM_RESOURCE_KIND], ()> {
314 let base_fee = self.base_fee_wei()?;
315 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
316 let mut fees = [U256::ZERO; NUM_RESOURCE_KIND];
317 for kind in ResourceKind::ALL {
318 if kind == ResourceKind::L1Calldata {
320 fees[kind as usize] = base_fee;
321 continue;
322 }
323 let fee = mgf.get_current_block_fee(kind)?;
324 fees[kind as usize] = if fee.is_zero() { base_fee } else { fee };
325 }
326 Ok(fees)
327 }
328
329 pub fn commit_multi_gas_fees(&self) -> Result<(), ()> {
333 if self.gas_model_to_use()? != GasModel::MultiGasConstraints {
334 return Ok(());
335 }
336 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
337 mgf.commit_next_to_current()
338 }
339
340 pub fn backlog_update_cost(&self) -> Result<u64, ()> {
348 use super::{STORAGE_READ_COST, STORAGE_WRITE_COST};
349
350 if self.arbos_version >= version::ARBOS_VERSION_60 {
352 return Ok(MULTI_CONSTRAINT_STATIC_BACKLOG_UPDATE_COST);
353 }
354
355 let mut result = 0u64;
356
357 if self.arbos_version >= version::ARBOS_VERSION_50 {
359 result += STORAGE_READ_COST;
360 }
361
362 if self.arbos_version >= version::ARBOS_VERSION_MULTI_CONSTRAINT_FIX {
364 let constraints_length = self.gas_constraints_length()?;
365 if constraints_length > 0 {
366 result += STORAGE_READ_COST;
368 result += constraints_length * (STORAGE_READ_COST + STORAGE_WRITE_COST);
370 return Ok(result);
371 }
372 }
374
375 result += STORAGE_READ_COST + STORAGE_WRITE_COST;
377
378 Ok(result)
379 }
380
381 pub fn set_gas_constraints_from_legacy(&self) -> Result<(), ()> {
383 self.clear_gas_constraints()?;
384 let target = self.speed_limit_per_second()?;
385 let adjustment_window = self.pricing_inertia()?;
386 let old_backlog = self.gas_backlog()?;
387 let backlog_tolerance = self.backlog_tolerance()?;
388
389 let backlog = old_backlog.saturating_sub(backlog_tolerance.saturating_mul(target));
390 self.add_gas_constraint(target, adjustment_window, backlog)
391 }
392
393 pub fn set_multi_gas_constraints_from_single_gas_constraints(&self) -> Result<(), ()> {
399 self.clear_multi_gas_constraints()?;
400
401 let length = self.gas_constraints_length()?;
402
403 for i in 0..length {
404 let c = self.open_gas_constraint_at(i);
405
406 let target = c.target()?;
407 let window = c.adjustment_window()?;
408 let backlog = c.backlog()?;
409
410 let weights = [1u64; NUM_RESOURCE_KIND];
412
413 let adjustment_window: u32 = if window > u32::MAX as u64 {
415 u32::MAX
416 } else {
417 window as u32
418 };
419
420 self.add_multi_gas_constraint(target, adjustment_window, backlog, &weights)?;
421 }
422 Ok(())
423 }
424
425 pub fn multi_dimensional_price_for_refund(&self, gas_used: MultiGas) -> Result<U256, ()> {
429 let fees = self.get_multi_gas_base_fee_per_resource()?;
430 let mut total = U256::ZERO;
431 for kind in ResourceKind::ALL {
432 let amount = gas_used.get(kind);
433 if amount == 0 {
434 continue;
435 }
436 total = total.saturating_add(U256::from(amount).saturating_mul(fees[kind as usize]));
437 }
438 Ok(total)
439 }
440}
441
442fn approx_exp_basis_points(bips: u64) -> u64 {
446 const ACCURACY: u64 = 4;
447 const B: u64 = 10_000; if bips == 0 {
450 return B;
451 }
452
453 let mut res = B.saturating_add(bips / ACCURACY);
455 let mut i = ACCURACY - 1;
456 while i > 0 {
457 res = B.saturating_add(res.saturating_mul(bips) / (i * B));
458 i -= 1;
459 }
460
461 res
462}
463
464fn saturating_cast_to_i64(value: u64) -> i64 {
466 if value > i64::MAX as u64 {
467 i64::MAX
468 } else {
469 value as i64
470 }
471}
472
473fn natural_to_bips(natural: i64) -> i64 {
475 natural.saturating_mul(10000)
476}
477
478pub fn apply_gas_delta(backlog: u64, delta: i64) -> u64 {
480 if delta > 0 {
481 backlog.saturating_add(delta as u64)
482 } else {
483 backlog.saturating_sub((-delta) as u64)
484 }
485}
486
487fn apply_gas_delta_op(op: BacklogOperation, backlog: u64, delta: u64) -> u64 {
489 match op {
490 BacklogOperation::Grow => backlog.saturating_add(delta),
491 BacklogOperation::Shrink => backlog.saturating_sub(delta),
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use alloy_primitives::{address, keccak256, Address, B256, U256};
498 use arb_primitives::multigas::MultiGas;
499 use arb_storage::Storage;
500 use revm::{database::StateBuilder, Database};
501
502 const ARBOS_STATE_ADDRESS: Address = address!("A4B05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
503
504 #[derive(Default)]
505 struct EmptyDb;
506
507 impl Database for EmptyDb {
508 type Error = std::convert::Infallible;
509 fn basic(
510 &mut self,
511 _address: Address,
512 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
513 Ok(None)
514 }
515 fn code_by_hash(&mut self, _code_hash: B256) -> Result<revm::state::Bytecode, Self::Error> {
516 Ok(revm::state::Bytecode::default())
517 }
518 fn storage(&mut self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
519 Ok(U256::ZERO)
520 }
521 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
522 Ok(B256::ZERO)
523 }
524 }
525
526 fn ensure_cache_account(state: &mut revm::database::State<EmptyDb>, addr: Address) {
528 use revm::database::{states::account_status::AccountStatus, PlainAccount};
529
530 let _ = state.load_cache_account(addr);
531 if let Some(cached) = state.cache.accounts.get_mut(&addr) {
532 if cached.account.is_none() {
533 cached.account = Some(PlainAccount {
534 info: revm::state::AccountInfo {
535 balance: U256::ZERO,
536 nonce: 0,
537 code_hash: keccak256([]),
538 code: None,
539 account_id: None,
540 },
541 storage: Default::default(),
542 });
543 cached.status = AccountStatus::InMemoryChange;
544 }
545 }
546 }
547
548 #[test]
549 fn test_grow_backlog_through_l2_pricing_state() {
550 let mut state = StateBuilder::new()
551 .with_database(EmptyDb)
552 .with_bundle_update()
553 .build();
554
555 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
557 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
558
559 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
560
561 let backing = Storage::new(state_ptr, B256::ZERO);
563 let l2_sto = backing.open_sub_storage(&[1]);
564
565 super::super::initialize_l2_pricing_state(&l2_sto);
567
568 let l2_pricing = super::super::open_l2_pricing_state(
570 backing.open_sub_storage(&[1]),
571 10, );
573 let initial_backlog = l2_pricing.gas_backlog().unwrap();
574 assert_eq!(initial_backlog, 0, "Initial gasBacklog should be 0");
575
576 let result = l2_pricing.grow_backlog(100_000, MultiGas::default());
578 assert!(result.is_ok(), "grow_backlog should succeed");
579
580 let after_grow = l2_pricing.gas_backlog().unwrap();
582 assert_eq!(
583 after_grow, 100_000,
584 "gasBacklog should be 100000 after grow"
585 );
586
587 let result = l2_pricing.grow_backlog(50_000, MultiGas::default());
589 assert!(result.is_ok(), "second grow_backlog should succeed");
590
591 let after_second_grow = l2_pricing.gas_backlog().unwrap();
592 assert_eq!(
593 after_second_grow, 150_000,
594 "gasBacklog should be 150000 after second grow"
595 );
596
597 let result = l2_pricing.shrink_backlog(30_000, MultiGas::default());
599 assert!(result.is_ok(), "shrink_backlog should succeed");
600
601 let after_shrink = l2_pricing.gas_backlog().unwrap();
602 assert_eq!(
603 after_shrink, 120_000,
604 "gasBacklog should be 120000 after shrink"
605 );
606
607 use revm::database::states::bundle_state::BundleRetention;
609 state.merge_transitions(BundleRetention::Reverts);
610 let bundle = state.take_bundle();
611
612 let acct = bundle
613 .state
614 .get(&ARBOS_STATE_ADDRESS)
615 .expect("ArbOS account should be in bundle");
616
617 let l2_base = keccak256([1u8]); let gas_backlog_offset: u64 = 4;
621 let slot = arb_storage::storage_key_map(l2_base.as_slice(), gas_backlog_offset);
622
623 let bundle_slot = acct
624 .storage
625 .get(&slot)
626 .expect("gasBacklog slot should be in bundle");
627 assert_eq!(
628 bundle_slot.present_value,
629 U256::from(120_000u64),
630 "Bundle should contain final gasBacklog value"
631 );
632 }
633
634 #[test]
647 fn test_block_616862_backlog_survives_full_flow() {
648 use alloy_primitives::map::HashMap;
649 use revm::{database::states::bundle_state::BundleRetention, DatabaseCommit};
650
651 let l2_base = keccak256([1u8]); let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
654 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
655 let per_block_gas_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 1);
656 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
657 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
658 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
659 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
660
661 let version_slot = arb_storage::storage_key_map(&[], 0);
663
664 struct PreloadedDb {
666 slots: HashMap<(Address, U256), U256>,
667 }
668
669 impl PreloadedDb {
670 fn new() -> Self {
671 Self {
672 slots: HashMap::default(),
673 }
674 }
675 fn set(&mut self, addr: Address, slot: U256, val: U256) {
676 self.slots.insert((addr, slot), val);
677 }
678 }
679
680 impl Database for PreloadedDb {
681 type Error = std::convert::Infallible;
682 fn basic(
683 &mut self,
684 addr: Address,
685 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
686 if addr == ARBOS_STATE_ADDRESS {
687 Ok(Some(revm::state::AccountInfo {
688 nonce: 1,
689 balance: U256::ZERO,
690 code_hash: keccak256([]),
691 code: None,
692 account_id: None,
693 }))
694 } else {
695 Ok(None)
696 }
697 }
698 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
699 Ok(revm::state::Bytecode::default())
700 }
701 fn storage(&mut self, addr: Address, index: U256) -> Result<U256, Self::Error> {
702 Ok(self
703 .slots
704 .get(&(addr, index))
705 .copied()
706 .unwrap_or(U256::ZERO))
707 }
708 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
709 Ok(B256::ZERO)
710 }
711 }
712
713 let arbos = ARBOS_STATE_ADDRESS;
714
715 let mut db = PreloadedDb::new();
717 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
718 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
719 db.set(arbos, per_block_gas_limit_slot, U256::from(32_000_000u64));
720 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
721 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
722 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
723 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
724 db.set(arbos, version_slot, U256::from(20u64)); let mut state = StateBuilder::new()
727 .with_database(db)
728 .with_bundle_update()
729 .build();
730
731 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
732
733 {
738 let backing = Storage::new(state_ptr, B256::ZERO);
739 let l2_sto = backing.open_sub_storage(&[1]);
740 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
741
742 let result = l2_pricing.update_pricing_model(0, 20);
744 assert!(result.is_ok(), "update_pricing_model should succeed");
745
746 let backlog = l2_pricing.gas_backlog().unwrap();
748 assert_eq!(
749 backlog, 552_756,
750 "gasBacklog should be 552756 after no-op drain"
751 );
752 }
753
754 let empty_changes: HashMap<Address, revm::state::Account> = Default::default();
756 state.commit(empty_changes);
757
758 eprintln!("[TX0] After StartBlock commit");
759
760 {
765 let retryable_base = keccak256([2u8]); for i in 0u64..10 {
768 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
769 arb_storage::write_storage_at(
770 unsafe { &mut *state_ptr },
771 arbos,
772 slot,
773 U256::from(1000 + i),
774 );
775 }
776
777 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5); let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
780 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
781 arb_storage::write_storage_at(
782 unsafe { &mut *state_ptr },
783 arbos,
784 scratch_slot_1,
785 U256::from(42),
786 );
787 arb_storage::write_storage_at(
788 unsafe { &mut *state_ptr },
789 arbos,
790 scratch_slot_2,
791 U256::from(43),
792 );
793 arb_storage::write_storage_at(
794 unsafe { &mut *state_ptr },
795 arbos,
796 scratch_slot_3,
797 U256::from(44),
798 );
799 }
800
801 let empty_changes2: HashMap<Address, revm::state::Account> = Default::default();
803 state.commit(empty_changes2);
804
805 {
807 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
808 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
809 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
810 arb_storage::write_arbos_storage(
811 unsafe { &mut *state_ptr },
812 scratch_slot_1,
813 U256::ZERO,
814 );
815 arb_storage::write_arbos_storage(
816 unsafe { &mut *state_ptr },
817 scratch_slot_2,
818 U256::ZERO,
819 );
820 arb_storage::write_arbos_storage(
821 unsafe { &mut *state_ptr },
822 scratch_slot_3,
823 U256::ZERO,
824 );
825 }
826
827 eprintln!("[TX1] After SubmitRetryable commit + scratch clear");
828
829 {
835 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
836 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
837 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
838 arb_storage::write_storage_at(
839 unsafe { &mut *state_ptr },
840 arbos,
841 scratch_slot_1,
842 U256::from(99),
843 );
844 arb_storage::write_storage_at(
845 unsafe { &mut *state_ptr },
846 arbos,
847 scratch_slot_2,
848 U256::from(100),
849 );
850 arb_storage::write_storage_at(
851 unsafe { &mut *state_ptr },
852 arbos,
853 scratch_slot_3,
854 U256::from(101),
855 );
856 }
857
858 {
861 let mut evm_changes: HashMap<Address, revm::state::Account> = Default::default();
862
863 let sender = address!("fd86e9a33fd52e4085fb94d24b759448a621cd36");
865 let _ = state.load_cache_account(sender);
866 let mut sender_acct = revm::state::Account::default();
867 sender_acct.info.balance = U256::from(1_000_000_000u64);
868 sender_acct.info.nonce = 1;
869 sender_acct.mark_touch();
870 evm_changes.insert(sender, sender_acct);
871
872 let contracts = [
874 address!("4453d0eaf066a61c9b81ddc18bb5a2bf2fc52224"),
875 address!("7c7db13e5d385bcc797422d3c767856d15d24c5c"),
876 address!("0057892cb8bb5f1ce1b3c6f5ade899732249713f"),
877 address!("35aa95ac4747d928e2cd42fe4461f6d9d1826346"),
878 address!("e1e3b1cbacc870cb6e5f4bdf246feb6eb5cd351b"),
879 address!("7348fdf6f3e090c635b23d970945093455214f3b"),
880 address!("d50e4a971bc8ed55af6aebc0a2178456069e87b5"),
881 ];
882
883 for (i, &contract) in contracts.iter().enumerate() {
884 let _ = state.load_cache_account(contract);
885 let mut acct = revm::state::Account::default();
886 acct.info.nonce = 1;
887 acct.info.code_hash = keccak256(format!("code_{}", i).as_bytes());
888 acct.mark_touch();
889 for j in 0u64..3 {
891 let slot = U256::from(j);
892 let mut evm_slot =
893 revm::state::EvmStorageSlot::new(U256::from(i as u64 * 100 + j), 0);
894 evm_slot.present_value = U256::from(i as u64 * 100 + j + 1);
895 acct.storage.insert(slot, evm_slot);
896 }
897 evm_changes.insert(contract, acct);
898 }
899
900 state.commit(evm_changes);
901 }
902
903 eprintln!("[TX2] After RetryTx EVM commit ({} accounts)", 8);
904
905 {
907 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
908 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
909 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
910 arb_storage::write_arbos_storage(
911 unsafe { &mut *state_ptr },
912 scratch_slot_1,
913 U256::ZERO,
914 );
915 arb_storage::write_arbos_storage(
916 unsafe { &mut *state_ptr },
917 scratch_slot_2,
918 U256::ZERO,
919 );
920 arb_storage::write_arbos_storage(
921 unsafe { &mut *state_ptr },
922 scratch_slot_3,
923 U256::ZERO,
924 );
925 }
926
927 {
929 let retryable_base = keccak256([2u8]);
930 for i in 0u64..10 {
931 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
932 arb_storage::write_storage_at(unsafe { &mut *state_ptr }, arbos, slot, U256::ZERO);
933 }
934 }
935
936 {
938 let backing = Storage::new(state_ptr, B256::ZERO);
939 let l2_sto = backing.open_sub_storage(&[1]);
940 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
941
942 let backlog_before = l2_pricing.gas_backlog().unwrap();
943 eprintln!("[TX2] gasBacklog BEFORE grow: {}", backlog_before);
944 assert_eq!(
945 backlog_before, 552_756,
946 "gasBacklog should still be 552756 before grow"
947 );
948
949 let result = l2_pricing.grow_backlog(357_751, MultiGas::default());
950 assert!(result.is_ok(), "grow_backlog should succeed");
951
952 let backlog_after = l2_pricing.gas_backlog().unwrap();
953 eprintln!("[TX2] gasBacklog AFTER grow: {}", backlog_after);
954 assert_eq!(
955 backlog_after, 910_507,
956 "gasBacklog should be 910507 after grow"
957 );
958 }
959
960 eprintln!("[FINAL] Checking bundle...");
961
962 state.merge_transitions(BundleRetention::Reverts);
966 let mut bundle = state.take_bundle();
967
968 let pre_filter_backlog = bundle
970 .state
971 .get(&arbos)
972 .and_then(|a| a.storage.get(&gas_backlog_slot))
973 .map(|s| s.present_value);
974 eprintln!("[BUNDLE] Pre-filter gasBacklog: {:?}", pre_filter_backlog);
975 assert_eq!(
976 pre_filter_backlog,
977 Some(U256::from(910_507u64)),
978 "gasBacklog should be in bundle before filter with value 910507"
979 );
980
981 for (_addr, account) in bundle.state.iter_mut() {
983 account
984 .storage
985 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
986 }
987
988 let post_filter_backlog = bundle
990 .state
991 .get(&arbos)
992 .and_then(|a| a.storage.get(&gas_backlog_slot))
993 .map(|s| s.present_value);
994 eprintln!("[BUNDLE] Post-filter gasBacklog: {:?}", post_filter_backlog);
995 assert_eq!(
996 post_filter_backlog,
997 Some(U256::from(910_507u64)),
998 "gasBacklog should survive filter_unchanged_storage with value 910507"
999 );
1000
1001 let original = bundle
1003 .state
1004 .get(&arbos)
1005 .and_then(|a| a.storage.get(&gas_backlog_slot))
1006 .map(|s| s.previous_or_original_value);
1007 eprintln!("[BUNDLE] gasBacklog original_value: {:?}", original);
1008 assert_eq!(
1009 original,
1010 Some(U256::from(552_756u64)),
1011 "gasBacklog original should be the pre-block DB value 552756"
1012 );
1013
1014 let cache_backlog = state
1018 .cache
1019 .accounts
1020 .get(&arbos)
1021 .and_then(|ca| ca.account.as_ref())
1022 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1023 eprintln!("[CACHE] gasBacklog in cache: {:?}", cache_backlog);
1024 assert_eq!(
1025 cache_backlog,
1026 Some(U256::from(910_507u64)),
1027 "gasBacklog should be in cache with value 910507"
1028 );
1029
1030 eprintln!("[PASS] Block 616862 flow: gasBacklog correctly persisted as 910507");
1031 }
1032
1033 #[test]
1037 fn test_block_616862_with_arbos_in_evm_commit() {
1038 use alloy_primitives::map::HashMap;
1039 use revm::{database::states::bundle_state::BundleRetention, DatabaseCommit};
1040
1041 let l2_base = keccak256([1u8]);
1042 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
1043 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
1044 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
1045 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
1046 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
1047 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
1048 let version_slot = arb_storage::storage_key_map(&[], 0);
1049 let scratch_1 = arb_storage::storage_key_map(&[], 5);
1051 let scratch_2 = arb_storage::storage_key_map(&[], 6);
1052
1053 struct PreloadedDb {
1054 slots: HashMap<(Address, U256), U256>,
1055 }
1056 impl PreloadedDb {
1057 fn new() -> Self {
1058 Self {
1059 slots: HashMap::default(),
1060 }
1061 }
1062 fn set(&mut self, addr: Address, slot: U256, val: U256) {
1063 self.slots.insert((addr, slot), val);
1064 }
1065 }
1066 impl Database for PreloadedDb {
1067 type Error = std::convert::Infallible;
1068 fn basic(
1069 &mut self,
1070 addr: Address,
1071 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
1072 if addr == ARBOS_STATE_ADDRESS {
1073 Ok(Some(revm::state::AccountInfo {
1074 nonce: 1,
1075 balance: U256::ZERO,
1076 code_hash: keccak256([]),
1077 code: None,
1078 account_id: None,
1079 }))
1080 } else {
1081 Ok(None)
1082 }
1083 }
1084 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
1085 Ok(revm::state::Bytecode::default())
1086 }
1087 fn storage(&mut self, addr: Address, index: U256) -> Result<U256, Self::Error> {
1088 Ok(self
1089 .slots
1090 .get(&(addr, index))
1091 .copied()
1092 .unwrap_or(U256::ZERO))
1093 }
1094 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
1095 Ok(B256::ZERO)
1096 }
1097 }
1098
1099 let arbos = ARBOS_STATE_ADDRESS;
1100 let mut db = PreloadedDb::new();
1101 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
1102 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
1103 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
1104 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
1105 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
1106 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
1107 db.set(arbos, version_slot, U256::from(20u64));
1108
1109 let mut state = StateBuilder::new()
1110 .with_database(db)
1111 .with_bundle_update()
1112 .build();
1113 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
1114
1115 {
1117 let backing = Storage::new(state_ptr, B256::ZERO);
1118 let l2_sto = backing.open_sub_storage(&[1]);
1119 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1120 let _ = l2_pricing.update_pricing_model(0, 20);
1121 }
1122 state.commit(HashMap::default());
1123
1124 {
1126 arb_storage::write_storage_at(
1127 unsafe { &mut *state_ptr },
1128 arbos,
1129 scratch_1,
1130 U256::from(42),
1131 );
1132 arb_storage::write_storage_at(
1133 unsafe { &mut *state_ptr },
1134 arbos,
1135 scratch_2,
1136 U256::from(43),
1137 );
1138 let retryable_base = keccak256([2u8]);
1139 for i in 0u64..5 {
1140 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
1141 arb_storage::write_storage_at(
1142 unsafe { &mut *state_ptr },
1143 arbos,
1144 slot,
1145 U256::from(1000 + i),
1146 );
1147 }
1148 }
1149 state.commit(HashMap::default());
1150 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_1, U256::ZERO);
1152 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_2, U256::ZERO);
1153
1154 arb_storage::write_storage_at(unsafe { &mut *state_ptr }, arbos, scratch_1, U256::from(99));
1156 arb_storage::write_storage_at(
1157 unsafe { &mut *state_ptr },
1158 arbos,
1159 scratch_2,
1160 U256::from(100),
1161 );
1162
1163 {
1165 let mut evm_changes: HashMap<Address, revm::state::Account> = Default::default();
1166
1167 let sender = address!("fd86e9a33fd52e4085fb94d24b759448a621cd36");
1169 let _ = state.load_cache_account(sender);
1170 let mut sender_acct = revm::state::Account::default();
1171 sender_acct.info.balance = U256::from(1_000_000_000u64);
1172 sender_acct.info.nonce = 1;
1173 sender_acct.mark_touch();
1174 evm_changes.insert(sender, sender_acct);
1175
1176 let _ = state.load_cache_account(arbos);
1179 let mut arbos_acct = revm::state::Account {
1180 info: revm::state::AccountInfo {
1181 nonce: 1,
1182 balance: U256::ZERO,
1183 code_hash: keccak256([]),
1184 code: None,
1185 account_id: None,
1186 },
1187 ..Default::default()
1188 };
1189 arbos_acct.storage.insert(
1192 scratch_1,
1193 revm::state::EvmStorageSlot::new(U256::from(99), 0),
1194 );
1195 arbos_acct.mark_touch();
1196 evm_changes.insert(arbos, arbos_acct);
1197
1198 state.commit(evm_changes);
1199 }
1200
1201 eprintln!("[VARIANT] After EVM commit with ArbOS account included");
1202
1203 let backlog_check =
1205 arb_storage::read_storage_at(unsafe { &mut *state_ptr }, arbos, gas_backlog_slot);
1206 eprintln!("[VARIANT] gasBacklog after EVM commit: {}", backlog_check);
1207
1208 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_1, U256::ZERO);
1210 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_2, U256::ZERO);
1211
1212 {
1214 let retryable_base = keccak256([2u8]);
1215 for i in 0u64..5 {
1216 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
1217 arb_storage::write_storage_at(unsafe { &mut *state_ptr }, arbos, slot, U256::ZERO);
1218 }
1219 }
1220
1221 {
1223 let backing = Storage::new(state_ptr, B256::ZERO);
1224 let l2_sto = backing.open_sub_storage(&[1]);
1225 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1226
1227 let backlog_before = l2_pricing.gas_backlog().unwrap();
1228 eprintln!("[VARIANT] gasBacklog BEFORE grow: {}", backlog_before);
1229 assert_eq!(
1230 backlog_before, 552_756,
1231 "gasBacklog should be 552756 before grow"
1232 );
1233
1234 let _ = l2_pricing.grow_backlog(357_751, MultiGas::default());
1235
1236 let backlog_after = l2_pricing.gas_backlog().unwrap();
1237 eprintln!("[VARIANT] gasBacklog AFTER grow: {}", backlog_after);
1238 assert_eq!(backlog_after, 910_507, "gasBacklog should be 910507");
1239 }
1240
1241 state.merge_transitions(BundleRetention::Reverts);
1243 let mut bundle = state.take_bundle();
1244
1245 let pre_filter = bundle
1246 .state
1247 .get(&arbos)
1248 .and_then(|a| a.storage.get(&gas_backlog_slot))
1249 .map(|s| (s.present_value, s.previous_or_original_value));
1250 eprintln!("[VARIANT] Pre-filter gasBacklog: {:?}", pre_filter);
1251 assert_eq!(
1252 pre_filter.map(|p| p.0),
1253 Some(U256::from(910_507u64)),
1254 "gasBacklog should be 910507 in bundle before filter"
1255 );
1256
1257 for (_addr, account) in bundle.state.iter_mut() {
1259 account
1260 .storage
1261 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1262 }
1263
1264 let post_filter = bundle
1265 .state
1266 .get(&arbos)
1267 .and_then(|a| a.storage.get(&gas_backlog_slot))
1268 .map(|s| s.present_value);
1269 eprintln!("[VARIANT] Post-filter gasBacklog: {:?}", post_filter);
1270 assert_eq!(
1271 post_filter,
1272 Some(U256::from(910_507u64)),
1273 "gasBacklog should survive filter when ArbOS is in EVM commit"
1274 );
1275
1276 eprintln!("[PASS] Variant with ArbOS in EVM commit: gasBacklog correctly persisted");
1277 }
1278
1279 #[test]
1284 fn test_block_616862_transition_state_consumed() {
1285 use alloy_primitives::map::HashMap;
1286 use revm::{
1287 database::states::{bundle_state::BundleRetention, plain_account::StorageSlot},
1288 DatabaseCommit,
1289 };
1290
1291 let l2_base = keccak256([1u8]);
1292 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
1293 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
1294 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
1295 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
1296 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
1297 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
1298 let version_slot = arb_storage::storage_key_map(&[], 0);
1299
1300 struct PreloadedDb(HashMap<(Address, U256), U256>);
1301 impl PreloadedDb {
1302 fn new() -> Self {
1303 Self(HashMap::default())
1304 }
1305 fn set(&mut self, a: Address, s: U256, v: U256) {
1306 self.0.insert((a, s), v);
1307 }
1308 }
1309 impl Database for PreloadedDb {
1310 type Error = std::convert::Infallible;
1311 fn basic(
1312 &mut self,
1313 addr: Address,
1314 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
1315 if addr == ARBOS_STATE_ADDRESS {
1316 Ok(Some(revm::state::AccountInfo {
1317 nonce: 1,
1318 balance: U256::ZERO,
1319 code_hash: keccak256([]),
1320 code: None,
1321 account_id: None,
1322 }))
1323 } else {
1324 Ok(None)
1325 }
1326 }
1327 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
1328 Ok(revm::state::Bytecode::default())
1329 }
1330 fn storage(&mut self, a: Address, i: U256) -> Result<U256, Self::Error> {
1331 Ok(self.0.get(&(a, i)).copied().unwrap_or(U256::ZERO))
1332 }
1333 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
1334 Ok(B256::ZERO)
1335 }
1336 }
1337
1338 let arbos = ARBOS_STATE_ADDRESS;
1339 let mut db = PreloadedDb::new();
1340 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
1341 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
1342 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
1343 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
1344 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
1345 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
1346 db.set(arbos, version_slot, U256::from(20u64));
1347
1348 let mut state = StateBuilder::new()
1349 .with_database(db)
1350 .with_bundle_update()
1351 .build();
1352 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
1353
1354 {
1356 let backing = Storage::new(state_ptr, B256::ZERO);
1357 let l2_sto = backing.open_sub_storage(&[1]);
1358 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1359 let _ = l2_pricing.update_pricing_model(0, 20);
1360 }
1361 state.commit(HashMap::default());
1362
1363 state.merge_transitions(BundleRetention::Reverts);
1368 let _mid_bundle = state.take_bundle();
1369 eprintln!("[BUG-SIM] transition_state consumed mid-block!");
1370
1371 let ts_is_none = state.transition_state.is_none();
1373 eprintln!("[BUG-SIM] transition_state is None: {}", ts_is_none);
1374
1375 {
1377 let backing = Storage::new(state_ptr, B256::ZERO);
1378 let l2_sto = backing.open_sub_storage(&[1]);
1379 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1380
1381 let backlog_before = l2_pricing.gas_backlog().unwrap();
1382 eprintln!("[BUG-SIM] gasBacklog before grow: {}", backlog_before);
1383
1384 let _ = l2_pricing.grow_backlog(357_751, MultiGas::default());
1385
1386 let backlog_after = l2_pricing.gas_backlog().unwrap();
1387 eprintln!(
1388 "[BUG-SIM] gasBacklog after grow: {} (from cache)",
1389 backlog_after
1390 );
1391 }
1392
1393 state.merge_transitions(BundleRetention::Reverts);
1395 let mut bundle = state.take_bundle();
1396
1397 let in_bundle = bundle
1399 .state
1400 .get(&arbos)
1401 .and_then(|a| a.storage.get(&gas_backlog_slot))
1402 .map(|s| s.present_value);
1403 eprintln!(
1404 "[BUG-SIM] gasBacklog in bundle after 2nd merge: {:?}",
1405 in_bundle
1406 );
1407
1408 {
1412 let cache_val = state
1413 .cache
1414 .accounts
1415 .get(&arbos)
1416 .and_then(|ca| ca.account.as_ref())
1417 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1418 eprintln!("[BUG-SIM] gasBacklog in cache: {:?}", cache_val);
1419
1420 if let Some(bundle_acct) = bundle.state.get_mut(&arbos) {
1422 if let Some(cached_acc) = state.cache.accounts.get(&arbos) {
1423 if let Some(ref plain) = cached_acc.account {
1424 for (key, value) in &plain.storage {
1425 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1426 slot.present_value = *value;
1427 } else {
1428 let original =
1429 state.database.storage(arbos, *key).unwrap_or(U256::ZERO);
1430 if *value != original {
1431 bundle_acct.storage.insert(
1432 *key,
1433 StorageSlot {
1434 previous_or_original_value: original,
1435 present_value: *value,
1436 },
1437 );
1438 }
1439 }
1440 }
1441 }
1442 }
1443 } else {
1444 if let Some(cached_acc) = state.cache.accounts.get(&arbos) {
1446 if let Some(ref plain) = cached_acc.account {
1447 let mut storage_changes: HashMap<U256, StorageSlot> = HashMap::default();
1448 for (key, value) in &plain.storage {
1449 let original =
1450 state.database.storage(arbos, *key).unwrap_or(U256::ZERO);
1451 if *value != original {
1452 storage_changes.insert(
1453 *key,
1454 StorageSlot {
1455 previous_or_original_value: original,
1456 present_value: *value,
1457 },
1458 );
1459 }
1460 }
1461 if !storage_changes.is_empty() {
1462 bundle.state.insert(
1463 arbos,
1464 revm::database::BundleAccount {
1465 info: Some(plain.info.clone()),
1466 original_info: None,
1467 storage: storage_changes,
1468 status: revm::database::AccountStatus::Changed,
1469 },
1470 );
1471 }
1472 }
1473 }
1474 }
1475 }
1476
1477 let after_augment = bundle
1478 .state
1479 .get(&arbos)
1480 .and_then(|a| a.storage.get(&gas_backlog_slot))
1481 .map(|s| s.present_value);
1482 eprintln!("[BUG-SIM] gasBacklog after augment: {:?}", after_augment);
1483
1484 for (_addr, account) in bundle.state.iter_mut() {
1486 account
1487 .storage
1488 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1489 }
1490
1491 let after_filter = bundle
1492 .state
1493 .get(&arbos)
1494 .and_then(|a| a.storage.get(&gas_backlog_slot))
1495 .map(|s| s.present_value);
1496 eprintln!("[BUG-SIM] gasBacklog after filter: {:?}", after_filter);
1497
1498 assert_eq!(
1499 after_filter,
1500 Some(U256::from(910_507u64)),
1501 "gasBacklog MUST survive even when transition_state was consumed mid-block"
1502 );
1503
1504 eprintln!("[PASS] Bug simulation: augment_bundle_from_cache rescued gasBacklog");
1505 }
1506
1507 #[test]
1515 fn test_grow_backlog_survives_evm_commit_and_augment() {
1516 use revm::{
1517 database::states::{bundle_state::BundleRetention, plain_account::StorageSlot},
1518 DatabaseCommit,
1519 };
1520
1521 let l2_base = keccak256([1u8]); let gas_backlog_offset: u64 = 4;
1524 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), gas_backlog_offset);
1525
1526 eprintln!("[TEST] gas_backlog_slot = {:?}", gas_backlog_slot);
1527
1528 eprintln!("\n===== VARIANT A: Empty EVM commit =====");
1530 {
1531 let mut state = StateBuilder::new()
1532 .with_database(EmptyDb)
1533 .with_bundle_update()
1534 .build();
1535
1536 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1538 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1539
1540 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1541
1542 let backing = Storage::new(state_ptr, B256::ZERO);
1544 let l2_sto = backing.open_sub_storage(&[1]);
1545 super::super::initialize_l2_pricing_state(&l2_sto);
1546
1547 let l2_pricing =
1549 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1550 l2_pricing.set_gas_backlog(552756).unwrap();
1551 let pre_start = l2_pricing.gas_backlog().unwrap();
1552 assert_eq!(pre_start, 552756, "Pre-existing backlog should be 552756");
1553 eprintln!("[A] Step 3: gas_backlog set to {}", pre_start);
1554
1555 l2_pricing.update_pricing_model(0, 10).unwrap();
1557 let after_start = l2_pricing.gas_backlog().unwrap();
1558 eprintln!(
1559 "[A] Step 4: after update_pricing_model(0): gas_backlog={}",
1560 after_start
1561 );
1562 assert_eq!(
1563 after_start, 552756,
1564 "time_passed=0 should not change backlog"
1565 );
1566
1567 let empty_state: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1569 Default::default();
1570 state.commit(empty_state);
1571 eprintln!("[A] Step 5: committed empty EVM state");
1572
1573 let cache_val = state
1575 .cache
1576 .accounts
1577 .get(&ARBOS_STATE_ADDRESS)
1578 .and_then(|ca| ca.account.as_ref())
1579 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1580 eprintln!(
1581 "[A] Step 5 cache check: gas_backlog in cache = {:?}",
1582 cache_val
1583 );
1584
1585 let l2_pricing2 =
1587 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1588 l2_pricing2
1589 .grow_backlog(357751, MultiGas::default())
1590 .unwrap();
1591 let after_grow = l2_pricing2.gas_backlog().unwrap();
1592 eprintln!(
1593 "[A] Step 6: after grow_backlog(357751): gas_backlog={}",
1594 after_grow
1595 );
1596 assert_eq!(after_grow, 552756 + 357751, "backlog should be sum");
1597
1598 let cache_val2 = state
1600 .cache
1601 .accounts
1602 .get(&ARBOS_STATE_ADDRESS)
1603 .and_then(|ca| ca.account.as_ref())
1604 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1605 eprintln!(
1606 "[A] Step 6 cache check: gas_backlog in cache = {:?}",
1607 cache_val2
1608 );
1609
1610 state.merge_transitions(BundleRetention::Reverts);
1612 let mut bundle = state.take_bundle();
1613 eprintln!("[A] Step 7: bundle has {} accounts", bundle.state.len());
1614
1615 let bundle_has_slot = bundle
1617 .state
1618 .get(&ARBOS_STATE_ADDRESS)
1619 .and_then(|a| a.storage.get(&gas_backlog_slot))
1620 .map(|s| s.present_value);
1621 eprintln!(
1622 "[A] Step 7 bundle pre-augment: gas_backlog = {:?}",
1623 bundle_has_slot
1624 );
1625
1626 for (addr, cache_acct) in &state.cache.accounts {
1629 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1630 let current_storage = cache_acct
1631 .account
1632 .as_ref()
1633 .map(|a| &a.storage)
1634 .cloned()
1635 .unwrap_or_default();
1636
1637 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1638 bundle_acct.info = current_info;
1639 for (key, value) in ¤t_storage {
1640 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1641 slot.present_value = *value;
1642 } else {
1643 let original_value = U256::ZERO;
1646 if *value != original_value {
1647 bundle_acct.storage.insert(
1648 *key,
1649 StorageSlot {
1650 previous_or_original_value: original_value,
1651 present_value: *value,
1652 },
1653 );
1654 }
1655 }
1656 }
1657 } else {
1658 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
1660 current_storage
1661 .iter()
1662 .filter_map(|(key, value)| {
1663 let original_value = U256::ZERO;
1664 if original_value != *value {
1665 Some((
1666 *key,
1667 StorageSlot {
1668 previous_or_original_value: original_value,
1669 present_value: *value,
1670 },
1671 ))
1672 } else {
1673 None
1674 }
1675 })
1676 .collect();
1677
1678 let info_changed = current_info.is_some(); if info_changed || !storage_changes.is_empty() {
1680 bundle.state.insert(
1681 *addr,
1682 revm::database::BundleAccount {
1683 info: current_info,
1684 original_info: None,
1685 storage: storage_changes,
1686 status: revm::database::AccountStatus::InMemoryChange,
1687 },
1688 );
1689 }
1690 }
1691 }
1692
1693 let bundle_after_augment = bundle
1694 .state
1695 .get(&ARBOS_STATE_ADDRESS)
1696 .and_then(|a| a.storage.get(&gas_backlog_slot))
1697 .map(|s| (s.present_value, s.previous_or_original_value));
1698 eprintln!(
1699 "[A] Step 8 bundle post-augment: gas_backlog = {:?}",
1700 bundle_after_augment
1701 );
1702
1703 for (_addr, account) in bundle.state.iter_mut() {
1705 account
1706 .storage
1707 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1708 }
1709
1710 let final_slot = bundle
1711 .state
1712 .get(&ARBOS_STATE_ADDRESS)
1713 .and_then(|a| a.storage.get(&gas_backlog_slot))
1714 .map(|s| s.present_value);
1715 eprintln!("[A] Step 9 FINAL: gas_backlog in bundle = {:?}", final_slot);
1716 assert!(
1717 final_slot.is_some(),
1718 "VARIANT A FAILED: gas_backlog slot MISSING from bundle after empty EVM commit"
1719 );
1720 assert_eq!(
1721 final_slot.unwrap(),
1722 U256::from(552756u64 + 357751u64),
1723 "VARIANT A: gas_backlog should be 910507"
1724 );
1725 }
1726
1727 eprintln!("\n===== VARIANT B: EVM commit with ArbOS account touched =====");
1729 {
1730 let mut state = StateBuilder::new()
1731 .with_database(EmptyDb)
1732 .with_bundle_update()
1733 .build();
1734
1735 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1736 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1737
1738 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1739
1740 let backing = Storage::new(state_ptr, B256::ZERO);
1741 let l2_sto = backing.open_sub_storage(&[1]);
1742 super::super::initialize_l2_pricing_state(&l2_sto);
1743
1744 let l2_pricing =
1745 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1746 l2_pricing.set_gas_backlog(552756).unwrap();
1747 l2_pricing.update_pricing_model(0, 10).unwrap();
1748 let before_commit = l2_pricing.gas_backlog().unwrap();
1749 eprintln!("[B] Pre-commit: gas_backlog={}", before_commit);
1750
1751 let cache_slots_before = state
1753 .cache
1754 .accounts
1755 .get(&ARBOS_STATE_ADDRESS)
1756 .and_then(|ca| ca.account.as_ref())
1757 .map(|a| a.storage.len())
1758 .unwrap_or(0);
1759 eprintln!("[B] Cache slots before EVM commit: {}", cache_slots_before);
1760
1761 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
1766 let mut arbos_evm_account = revm::state::Account {
1767 info: revm::state::AccountInfo {
1768 balance: U256::ZERO,
1769 nonce: 1,
1770 code_hash: keccak256([]),
1771 code: None,
1772 account_id: None,
1773 },
1774 ..Default::default()
1775 };
1776 arbos_evm_account.mark_touch();
1777 let mut evm_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1779 Default::default();
1780 evm_changes.insert(ARBOS_STATE_ADDRESS, arbos_evm_account);
1781 state.commit(evm_changes);
1782 eprintln!("[B] Committed EVM state with ArbOS account touched");
1783
1784 let cache_slots_after = state
1786 .cache
1787 .accounts
1788 .get(&ARBOS_STATE_ADDRESS)
1789 .and_then(|ca| ca.account.as_ref())
1790 .map(|a| a.storage.len())
1791 .unwrap_or(0);
1792 eprintln!(
1793 "[B] Cache slots AFTER EVM commit: {} (was {})",
1794 cache_slots_after, cache_slots_before
1795 );
1796
1797 let cache_val = state
1798 .cache
1799 .accounts
1800 .get(&ARBOS_STATE_ADDRESS)
1801 .and_then(|ca| ca.account.as_ref())
1802 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1803 eprintln!("[B] gas_backlog in cache after EVM commit: {:?}", cache_val);
1804
1805 if cache_slots_after < cache_slots_before {
1806 eprintln!(
1807 "[B] !!! CACHE SLOTS WERE LOST BY EVM COMMIT !!! ({} -> {})",
1808 cache_slots_before, cache_slots_after
1809 );
1810 }
1811
1812 let l2_pricing2 =
1814 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1815 let read_before_grow = l2_pricing2.gas_backlog().unwrap();
1816 eprintln!("[B] gas_backlog read before grow: {}", read_before_grow);
1817
1818 l2_pricing2
1819 .grow_backlog(357751, MultiGas::default())
1820 .unwrap();
1821 let after_grow = l2_pricing2.gas_backlog().unwrap();
1822 eprintln!("[B] gas_backlog after grow: {}", after_grow);
1823
1824 let cache_val2 = state
1826 .cache
1827 .accounts
1828 .get(&ARBOS_STATE_ADDRESS)
1829 .and_then(|ca| ca.account.as_ref())
1830 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1831 eprintln!("[B] gas_backlog in cache after grow: {:?}", cache_val2);
1832
1833 state.merge_transitions(BundleRetention::Reverts);
1835 let mut bundle = state.take_bundle();
1836
1837 let bundle_pre = bundle
1838 .state
1839 .get(&ARBOS_STATE_ADDRESS)
1840 .and_then(|a| a.storage.get(&gas_backlog_slot))
1841 .map(|s| (s.present_value, s.previous_or_original_value));
1842 eprintln!("[B] Bundle pre-augment: gas_backlog = {:?}", bundle_pre);
1843
1844 for (addr, cache_acct) in &state.cache.accounts {
1846 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1847 let current_storage = cache_acct
1848 .account
1849 .as_ref()
1850 .map(|a| &a.storage)
1851 .cloned()
1852 .unwrap_or_default();
1853
1854 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1855 bundle_acct.info = current_info;
1856 for (key, value) in ¤t_storage {
1857 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1858 slot.present_value = *value;
1859 } else {
1860 let original_value = U256::ZERO;
1861 if *value != original_value {
1862 bundle_acct.storage.insert(
1863 *key,
1864 StorageSlot {
1865 previous_or_original_value: original_value,
1866 present_value: *value,
1867 },
1868 );
1869 }
1870 }
1871 }
1872 }
1873 }
1874
1875 let bundle_post = bundle
1876 .state
1877 .get(&ARBOS_STATE_ADDRESS)
1878 .and_then(|a| a.storage.get(&gas_backlog_slot))
1879 .map(|s| (s.present_value, s.previous_or_original_value));
1880 eprintln!("[B] Bundle post-augment: gas_backlog = {:?}", bundle_post);
1881
1882 for (_addr, account) in bundle.state.iter_mut() {
1884 account
1885 .storage
1886 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1887 }
1888
1889 let final_slot = bundle
1890 .state
1891 .get(&ARBOS_STATE_ADDRESS)
1892 .and_then(|a| a.storage.get(&gas_backlog_slot))
1893 .map(|s| s.present_value);
1894 eprintln!("[B] FINAL: gas_backlog in bundle = {:?}", final_slot);
1895 assert!(
1896 final_slot.is_some(),
1897 "VARIANT B FAILED: gas_backlog slot MISSING from bundle after EVM commit with ArbOS touched"
1898 );
1899 assert_eq!(
1900 final_slot.unwrap(),
1901 U256::from(552756u64 + 357751u64),
1902 "VARIANT B: gas_backlog should be 910507"
1903 );
1904 }
1905
1906 eprintln!("\n===== VARIANT C: EVM commit with ArbOS storage slot read (unchanged) =====");
1911 {
1912 let mut state = StateBuilder::new()
1913 .with_database(EmptyDb)
1914 .with_bundle_update()
1915 .build();
1916
1917 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1918 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1919
1920 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1921
1922 let backing = Storage::new(state_ptr, B256::ZERO);
1923 let l2_sto = backing.open_sub_storage(&[1]);
1924 super::super::initialize_l2_pricing_state(&l2_sto);
1925
1926 let l2_pricing =
1927 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1928 l2_pricing.set_gas_backlog(552756).unwrap();
1929 l2_pricing.update_pricing_model(0, 10).unwrap();
1930 eprintln!(
1931 "[C] Pre-commit: gas_backlog={}",
1932 l2_pricing.gas_backlog().unwrap()
1933 );
1934
1935 let cache_slots_before = state
1936 .cache
1937 .accounts
1938 .get(&ARBOS_STATE_ADDRESS)
1939 .and_then(|ca| ca.account.as_ref())
1940 .map(|a| a.storage.len())
1941 .unwrap_or(0);
1942 eprintln!("[C] Cache slots before: {}", cache_slots_before);
1943
1944 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
1947 let mut arbos_evm_account = revm::state::Account {
1948 info: revm::state::AccountInfo {
1949 balance: U256::ZERO,
1950 nonce: 1,
1951 code_hash: keccak256([]),
1952 code: None,
1953 account_id: None,
1954 },
1955 ..Default::default()
1956 };
1957 arbos_evm_account.mark_touch();
1958
1959 arbos_evm_account.storage.insert(
1962 gas_backlog_slot,
1963 revm::state::EvmStorageSlot::new(U256::from(552756u64), 0),
1964 );
1966
1967 let mut evm_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1968 Default::default();
1969 evm_changes.insert(ARBOS_STATE_ADDRESS, arbos_evm_account);
1970 state.commit(evm_changes);
1971 eprintln!("[C] Committed EVM state with ArbOS + read-only storage slot");
1972
1973 let cache_slots_after = state
1974 .cache
1975 .accounts
1976 .get(&ARBOS_STATE_ADDRESS)
1977 .and_then(|ca| ca.account.as_ref())
1978 .map(|a| a.storage.len())
1979 .unwrap_or(0);
1980 eprintln!(
1981 "[C] Cache slots AFTER: {} (was {})",
1982 cache_slots_after, cache_slots_before
1983 );
1984
1985 let cache_val = state
1986 .cache
1987 .accounts
1988 .get(&ARBOS_STATE_ADDRESS)
1989 .and_then(|ca| ca.account.as_ref())
1990 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1991 eprintln!("[C] gas_backlog in cache after commit: {:?}", cache_val);
1992
1993 if cache_slots_after < cache_slots_before {
1994 eprintln!(
1995 "[C] !!! CACHE SLOTS LOST !!! {} -> {}",
1996 cache_slots_before, cache_slots_after
1997 );
1998 }
1999
2000 let l2_pricing2 =
2002 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2003 let read_before_grow = l2_pricing2.gas_backlog().unwrap();
2004 eprintln!(
2005 "[C] gas_backlog read before grow: {} (expect 552756)",
2006 read_before_grow
2007 );
2008
2009 l2_pricing2
2010 .grow_backlog(357751, MultiGas::default())
2011 .unwrap();
2012 let after_grow = l2_pricing2.gas_backlog().unwrap();
2013 eprintln!("[C] gas_backlog after grow: {} (expect 910507)", after_grow);
2014
2015 state.merge_transitions(BundleRetention::Reverts);
2017 let mut bundle = state.take_bundle();
2018
2019 let bundle_pre = bundle
2020 .state
2021 .get(&ARBOS_STATE_ADDRESS)
2022 .and_then(|a| a.storage.get(&gas_backlog_slot))
2023 .map(|s| (s.present_value, s.previous_or_original_value));
2024 eprintln!("[C] Bundle pre-augment: gas_backlog = {:?}", bundle_pre);
2025
2026 for (addr, cache_acct) in &state.cache.accounts {
2028 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2029 let current_storage = cache_acct
2030 .account
2031 .as_ref()
2032 .map(|a| &a.storage)
2033 .cloned()
2034 .unwrap_or_default();
2035
2036 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2037 bundle_acct.info = current_info;
2038 for (key, value) in ¤t_storage {
2039 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2040 slot.present_value = *value;
2041 } else {
2042 let original_value = U256::ZERO;
2043 if *value != original_value {
2044 bundle_acct.storage.insert(
2045 *key,
2046 StorageSlot {
2047 previous_or_original_value: original_value,
2048 present_value: *value,
2049 },
2050 );
2051 }
2052 }
2053 }
2054 }
2055 }
2056
2057 let bundle_post = bundle
2058 .state
2059 .get(&ARBOS_STATE_ADDRESS)
2060 .and_then(|a| a.storage.get(&gas_backlog_slot))
2061 .map(|s| (s.present_value, s.previous_or_original_value));
2062 eprintln!("[C] Bundle post-augment: gas_backlog = {:?}", bundle_post);
2063
2064 for (_addr, account) in bundle.state.iter_mut() {
2066 account
2067 .storage
2068 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2069 }
2070
2071 let final_slot = bundle
2072 .state
2073 .get(&ARBOS_STATE_ADDRESS)
2074 .and_then(|a| a.storage.get(&gas_backlog_slot))
2075 .map(|s| s.present_value);
2076 eprintln!("[C] FINAL: gas_backlog in bundle = {:?}", final_slot);
2077 assert!(
2078 final_slot.is_some(),
2079 "VARIANT C FAILED: gas_backlog slot MISSING from bundle after EVM commit with ArbOS storage read"
2080 );
2081 assert_eq!(
2082 final_slot.unwrap(),
2083 U256::from(552756u64 + 357751u64),
2084 "VARIANT C: gas_backlog should be 910507"
2085 );
2086 }
2087
2088 eprintln!("\n===== VARIANT D: Two EVM commits then grow_backlog =====");
2091 {
2092 let mut state = StateBuilder::new()
2093 .with_database(EmptyDb)
2094 .with_bundle_update()
2095 .build();
2096
2097 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
2098 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
2099
2100 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
2101
2102 let backing = Storage::new(state_ptr, B256::ZERO);
2103 let l2_sto = backing.open_sub_storage(&[1]);
2104 super::super::initialize_l2_pricing_state(&l2_sto);
2105
2106 let l2_pricing =
2108 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2109 l2_pricing.set_gas_backlog(552756).unwrap();
2110
2111 l2_pricing.update_pricing_model(0, 10).unwrap();
2113 eprintln!(
2114 "[D] After StartBlock: backlog={}",
2115 l2_pricing.gas_backlog().unwrap()
2116 );
2117
2118 state.commit(Default::default());
2120 eprintln!("[D] Committed StartBlock (empty)");
2121
2122 let sender = address!("1111111111111111111111111111111111111111");
2124 let receiver = address!("2222222222222222222222222222222222222222");
2125 let _ = state.load_cache_account(sender);
2126 let _ = state.load_cache_account(receiver);
2127
2128 let mut user_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
2129 Default::default();
2130 let mut sender_acct = revm::state::Account::default();
2131 sender_acct.info.balance = U256::from(999_000u64);
2132 sender_acct.info.nonce = 1;
2133 sender_acct.mark_touch();
2134 user_changes.insert(sender, sender_acct);
2135
2136 let mut receiver_acct = revm::state::Account::default();
2137 receiver_acct.info.balance = U256::from(1_000u64);
2138 receiver_acct.mark_touch();
2139 user_changes.insert(receiver, receiver_acct);
2140
2141 state.commit(user_changes);
2142 eprintln!("[D] Committed user tx (sender+receiver)");
2143
2144 let cache_val_after_user = state
2146 .cache
2147 .accounts
2148 .get(&ARBOS_STATE_ADDRESS)
2149 .and_then(|ca| ca.account.as_ref())
2150 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
2151 eprintln!(
2152 "[D] gas_backlog in cache after user tx commit: {:?}",
2153 cache_val_after_user
2154 );
2155
2156 let l2_pricing2 =
2159 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2160 let read_val = l2_pricing2.gas_backlog().unwrap();
2161 eprintln!("[D] gas_backlog read before grow: {}", read_val);
2162 l2_pricing2
2163 .grow_backlog(357751, MultiGas::default())
2164 .unwrap();
2165 let after_grow = l2_pricing2.gas_backlog().unwrap();
2166 eprintln!("[D] gas_backlog after grow: {}", after_grow);
2167 assert_eq!(after_grow, 552756 + 357751, "backlog should be sum");
2168
2169 state.merge_transitions(BundleRetention::Reverts);
2171 let mut bundle = state.take_bundle();
2172
2173 let bundle_pre = bundle
2174 .state
2175 .get(&ARBOS_STATE_ADDRESS)
2176 .and_then(|a| a.storage.get(&gas_backlog_slot))
2177 .map(|s| (s.present_value, s.previous_or_original_value));
2178 eprintln!("[D] Bundle pre-augment: gas_backlog = {:?}", bundle_pre);
2179
2180 for (addr, cache_acct) in &state.cache.accounts {
2182 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2183 let current_storage = cache_acct
2184 .account
2185 .as_ref()
2186 .map(|a| &a.storage)
2187 .cloned()
2188 .unwrap_or_default();
2189
2190 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2191 bundle_acct.info = current_info;
2192 for (key, value) in ¤t_storage {
2193 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2194 slot.present_value = *value;
2195 } else {
2196 let original_value = U256::ZERO;
2197 if *value != original_value {
2198 bundle_acct.storage.insert(
2199 *key,
2200 StorageSlot {
2201 previous_or_original_value: original_value,
2202 present_value: *value,
2203 },
2204 );
2205 }
2206 }
2207 }
2208 } else {
2209 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
2210 current_storage
2211 .iter()
2212 .filter_map(|(key, value)| {
2213 let original_value = U256::ZERO;
2214 if original_value != *value {
2215 Some((
2216 *key,
2217 StorageSlot {
2218 previous_or_original_value: original_value,
2219 present_value: *value,
2220 },
2221 ))
2222 } else {
2223 None
2224 }
2225 })
2226 .collect();
2227 let info_changed = current_info.is_some();
2228 if info_changed || !storage_changes.is_empty() {
2229 bundle.state.insert(
2230 *addr,
2231 revm::database::BundleAccount {
2232 info: current_info,
2233 original_info: None,
2234 storage: storage_changes,
2235 status: revm::database::AccountStatus::InMemoryChange,
2236 },
2237 );
2238 }
2239 }
2240 }
2241
2242 let bundle_post = bundle
2243 .state
2244 .get(&ARBOS_STATE_ADDRESS)
2245 .and_then(|a| a.storage.get(&gas_backlog_slot))
2246 .map(|s| (s.present_value, s.previous_or_original_value));
2247 eprintln!("[D] Bundle post-augment: gas_backlog = {:?}", bundle_post);
2248
2249 for (_addr, account) in bundle.state.iter_mut() {
2251 account
2252 .storage
2253 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2254 }
2255
2256 let final_slot = bundle
2257 .state
2258 .get(&ARBOS_STATE_ADDRESS)
2259 .and_then(|a| a.storage.get(&gas_backlog_slot))
2260 .map(|s| s.present_value);
2261 eprintln!("[D] FINAL: gas_backlog in bundle = {:?}", final_slot);
2262 assert!(
2263 final_slot.is_some(),
2264 "VARIANT D FAILED: gas_backlog slot MISSING from bundle"
2265 );
2266 assert_eq!(
2267 final_slot.unwrap(),
2268 U256::from(552756u64 + 357751u64),
2269 "VARIANT D: gas_backlog should be 910507"
2270 );
2271 }
2272
2273 eprintln!("\n===== VARIANT E: Pre-existing DB state =====");
2279 {
2280 struct PrePopulatedDb {
2282 gas_backlog_slot: U256,
2283 pre_existing_backlog: U256,
2284 }
2285
2286 impl Database for PrePopulatedDb {
2287 type Error = std::convert::Infallible;
2288 fn basic(
2289 &mut self,
2290 _address: Address,
2291 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2292 Ok(Some(revm::state::AccountInfo {
2294 balance: U256::ZERO,
2295 nonce: 1,
2296 code_hash: keccak256([]),
2297 code: None,
2298 account_id: None,
2299 }))
2300 }
2301 fn code_by_hash(
2302 &mut self,
2303 _code_hash: B256,
2304 ) -> Result<revm::state::Bytecode, Self::Error> {
2305 Ok(revm::state::Bytecode::default())
2306 }
2307 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2308 if index == self.gas_backlog_slot {
2310 Ok(self.pre_existing_backlog)
2311 } else {
2312 Ok(U256::ZERO)
2313 }
2314 }
2315 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2316 Ok(B256::ZERO)
2317 }
2318 }
2319
2320 let pre_existing_backlog = U256::from(552756u64);
2321 let mut state = StateBuilder::new()
2322 .with_database(PrePopulatedDb {
2323 gas_backlog_slot,
2324 pre_existing_backlog,
2325 })
2326 .with_bundle_update()
2327 .build();
2328
2329 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2331
2332 let state_ptr: *mut revm::database::State<PrePopulatedDb> = &mut state;
2333
2334 let backing = Storage::new(state_ptr, B256::ZERO);
2336 let l2_pricing =
2337 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2338
2339 let current = l2_pricing.gas_backlog().unwrap();
2341 eprintln!("[E] Initial gas_backlog (from DB): {}", current);
2342 assert_eq!(current, 552756, "Should read from DB");
2343
2344 l2_pricing.update_pricing_model(0, 10).unwrap();
2346 let after_start = l2_pricing.gas_backlog().unwrap();
2347 eprintln!(
2348 "[E] After StartBlock (time_passed=0): gas_backlog={}",
2349 after_start
2350 );
2351
2352 {
2354 use revm::DatabaseCommit;
2355 let empty: alloy_primitives::map::HashMap<Address, revm::state::Account> =
2356 Default::default();
2357 state.commit(empty);
2358 }
2359
2360 {
2362 use revm::DatabaseCommit;
2363 let sender = address!("1111111111111111111111111111111111111111");
2364 let _ = state.load_cache_account(sender);
2365 let mut user_changes: alloy_primitives::map::HashMap<
2366 Address,
2367 revm::state::Account,
2368 > = Default::default();
2369 let mut sender_acct = revm::state::Account::default();
2370 sender_acct.info.balance = U256::from(999_000u64);
2371 sender_acct.info.nonce = 1;
2372 sender_acct.mark_touch();
2373 user_changes.insert(sender, sender_acct);
2374 state.commit(user_changes);
2375 }
2376
2377 eprintln!("[E] After two EVM commits");
2378
2379 let l2_pricing2 =
2381 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2382 let read_before = l2_pricing2.gas_backlog().unwrap();
2383 eprintln!("[E] gas_backlog before grow: {}", read_before);
2384
2385 l2_pricing2
2386 .grow_backlog(357751, MultiGas::default())
2387 .unwrap();
2388 let after_grow = l2_pricing2.gas_backlog().unwrap();
2389 eprintln!(
2390 "[E] gas_backlog after grow: {} (expect {})",
2391 after_grow,
2392 552756 + 357751
2393 );
2394
2395 let cache_val = state
2397 .cache
2398 .accounts
2399 .get(&ARBOS_STATE_ADDRESS)
2400 .and_then(|ca| ca.account.as_ref())
2401 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
2402 eprintln!("[E] gas_backlog in cache: {:?}", cache_val);
2403
2404 state.merge_transitions(BundleRetention::Reverts);
2406 let mut bundle = state.take_bundle();
2407
2408 let bundle_pre = bundle
2409 .state
2410 .get(&ARBOS_STATE_ADDRESS)
2411 .and_then(|a| a.storage.get(&gas_backlog_slot))
2412 .map(|s| (s.present_value, s.previous_or_original_value));
2413 eprintln!("[E] Bundle pre-augment: gas_backlog = {:?}", bundle_pre);
2414
2415 for (addr, cache_acct) in &state.cache.accounts {
2417 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2418 let current_storage = cache_acct
2419 .account
2420 .as_ref()
2421 .map(|a| &a.storage)
2422 .cloned()
2423 .unwrap_or_default();
2424
2425 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2426 bundle_acct.info = current_info;
2427 for (key, value) in ¤t_storage {
2428 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2429 slot.present_value = *value;
2430 } else {
2431 let original_value =
2433 if *addr == ARBOS_STATE_ADDRESS && *key == gas_backlog_slot {
2434 pre_existing_backlog
2435 } else {
2436 U256::ZERO
2437 };
2438 if *value != original_value {
2439 bundle_acct.storage.insert(
2440 *key,
2441 StorageSlot {
2442 previous_or_original_value: original_value,
2443 present_value: *value,
2444 },
2445 );
2446 }
2447 }
2448 }
2449 } else {
2450 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
2452 current_storage
2453 .iter()
2454 .filter_map(|(key, value)| {
2455 let original_value =
2456 if *addr == ARBOS_STATE_ADDRESS && *key == gas_backlog_slot {
2457 pre_existing_backlog
2458 } else {
2459 U256::ZERO
2460 };
2461 if original_value != *value {
2462 Some((
2463 *key,
2464 StorageSlot {
2465 previous_or_original_value: original_value,
2466 present_value: *value,
2467 },
2468 ))
2469 } else {
2470 None
2471 }
2472 })
2473 .collect();
2474 let info_changed = false; if info_changed || !storage_changes.is_empty() {
2476 bundle.state.insert(
2477 *addr,
2478 revm::database::BundleAccount {
2479 info: current_info,
2480 original_info: None,
2481 storage: storage_changes,
2482 status: revm::database::AccountStatus::Changed,
2483 },
2484 );
2485 }
2486 }
2487 }
2488
2489 let bundle_post = bundle
2490 .state
2491 .get(&ARBOS_STATE_ADDRESS)
2492 .and_then(|a| a.storage.get(&gas_backlog_slot))
2493 .map(|s| (s.present_value, s.previous_or_original_value));
2494 eprintln!("[E] Bundle post-augment: gas_backlog = {:?}", bundle_post);
2495
2496 for (_addr, account) in bundle.state.iter_mut() {
2498 account
2499 .storage
2500 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2501 }
2502
2503 let final_slot = bundle
2504 .state
2505 .get(&ARBOS_STATE_ADDRESS)
2506 .and_then(|a| a.storage.get(&gas_backlog_slot))
2507 .map(|s| s.present_value);
2508 eprintln!("[E] FINAL: gas_backlog in bundle = {:?}", final_slot);
2509 assert!(
2510 final_slot.is_some(),
2511 "VARIANT E FAILED: gas_backlog slot MISSING from bundle (pre-populated DB)"
2512 );
2513 assert_eq!(
2514 final_slot.unwrap(),
2515 U256::from(552756u64 + 357751u64),
2516 "VARIANT E: gas_backlog should be 910507"
2517 );
2518 }
2519
2520 eprintln!("\n===== VARIANT F: Pre-existing DB + StartBlock drain + grow =====");
2530 {
2531 struct PrePopulatedDb2 {
2532 gas_backlog_slot: U256,
2533 pre_existing_backlog: U256,
2534 speed_limit_slot: U256,
2535 speed_limit_value: U256,
2536 }
2537
2538 impl Database for PrePopulatedDb2 {
2539 type Error = std::convert::Infallible;
2540 fn basic(
2541 &mut self,
2542 _address: Address,
2543 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2544 Ok(Some(revm::state::AccountInfo {
2545 balance: U256::ZERO,
2546 nonce: 1,
2547 code_hash: keccak256([]),
2548 code: None,
2549 account_id: None,
2550 }))
2551 }
2552 fn code_by_hash(
2553 &mut self,
2554 _code_hash: B256,
2555 ) -> Result<revm::state::Bytecode, Self::Error> {
2556 Ok(revm::state::Bytecode::default())
2557 }
2558 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2559 if index == self.gas_backlog_slot {
2560 Ok(self.pre_existing_backlog)
2561 } else if index == self.speed_limit_slot {
2562 Ok(self.speed_limit_value)
2563 } else {
2564 Ok(U256::ZERO)
2565 }
2566 }
2567 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2568 Ok(B256::ZERO)
2569 }
2570 }
2571
2572 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0); let pre_existing_backlog = U256::from(552756u64);
2575
2576 let mut state = StateBuilder::new()
2577 .with_database(PrePopulatedDb2 {
2578 gas_backlog_slot,
2579 pre_existing_backlog,
2580 speed_limit_slot,
2581 speed_limit_value: U256::from(7_000_000u64), })
2583 .with_bundle_update()
2584 .build();
2585
2586 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2587 let state_ptr: *mut revm::database::State<PrePopulatedDb2> = &mut state;
2588
2589 let backing = Storage::new(state_ptr, B256::ZERO);
2590 let l2_pricing =
2591 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2592
2593 let initial = l2_pricing.gas_backlog().unwrap();
2594 eprintln!("[F] Initial gas_backlog: {}", initial);
2595
2596 l2_pricing.update_pricing_model(1, 10).unwrap();
2599 let after_drain = l2_pricing.gas_backlog().unwrap();
2600 eprintln!(
2601 "[F] After drain (time_passed=1, speed=7M): gas_backlog={} (was {})",
2602 after_drain, initial
2603 );
2604
2605 {
2607 use revm::DatabaseCommit;
2608 state.commit(Default::default());
2609 }
2610
2611 let l2_pricing2 =
2613 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2614 l2_pricing2
2615 .grow_backlog(357751, MultiGas::default())
2616 .unwrap();
2617 let after_grow = l2_pricing2.gas_backlog().unwrap();
2618 eprintln!("[F] After grow(357751): gas_backlog={}", after_grow);
2619
2620 state.merge_transitions(BundleRetention::Reverts);
2622 let mut bundle = state.take_bundle();
2623
2624 let bundle_pre = bundle
2625 .state
2626 .get(&ARBOS_STATE_ADDRESS)
2627 .and_then(|a| a.storage.get(&gas_backlog_slot))
2628 .map(|s| (s.present_value, s.previous_or_original_value));
2629 eprintln!("[F] Bundle pre-filter: gas_backlog = {:?}", bundle_pre);
2630
2631 for (_addr, account) in bundle.state.iter_mut() {
2637 account
2638 .storage
2639 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2640 }
2641
2642 let final_slot = bundle
2643 .state
2644 .get(&ARBOS_STATE_ADDRESS)
2645 .and_then(|a| a.storage.get(&gas_backlog_slot))
2646 .map(|s| (s.present_value, s.previous_or_original_value));
2647 eprintln!("[F] FINAL: gas_backlog = {:?}", final_slot);
2648
2649 assert!(
2653 final_slot.is_some(),
2654 "VARIANT F FAILED: gas_backlog slot MISSING from bundle (drain+grow)"
2655 );
2656 assert_eq!(
2657 final_slot.unwrap().0,
2658 U256::from(357751u64),
2659 "VARIANT F: gas_backlog should be 357751"
2660 );
2661 }
2662
2663 eprintln!("\n===== VARIANT G: Drain to 0 (no grow) - write not lost? =====");
2672 {
2673 struct PrePopDb3 {
2674 gas_backlog_slot: U256,
2675 speed_limit_slot: U256,
2676 }
2677
2678 impl Database for PrePopDb3 {
2679 type Error = std::convert::Infallible;
2680 fn basic(
2681 &mut self,
2682 _address: Address,
2683 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2684 Ok(Some(revm::state::AccountInfo {
2685 balance: U256::ZERO,
2686 nonce: 1,
2687 code_hash: keccak256([]),
2688 code: None,
2689 account_id: None,
2690 }))
2691 }
2692 fn code_by_hash(
2693 &mut self,
2694 _code_hash: B256,
2695 ) -> Result<revm::state::Bytecode, Self::Error> {
2696 Ok(revm::state::Bytecode::default())
2697 }
2698 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2699 if index == self.gas_backlog_slot {
2700 Ok(U256::from(552756u64))
2701 } else if index == self.speed_limit_slot {
2702 Ok(U256::from(7_000_000u64))
2703 } else {
2704 Ok(U256::ZERO)
2705 }
2706 }
2707 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2708 Ok(B256::ZERO)
2709 }
2710 }
2711
2712 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
2713
2714 let mut state = StateBuilder::new()
2715 .with_database(PrePopDb3 {
2716 gas_backlog_slot,
2717 speed_limit_slot,
2718 })
2719 .with_bundle_update()
2720 .build();
2721
2722 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2723 let state_ptr: *mut revm::database::State<PrePopDb3> = &mut state;
2724
2725 let backing = Storage::new(state_ptr, B256::ZERO);
2726 let l2_pricing =
2727 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2728
2729 let initial = l2_pricing.gas_backlog().unwrap();
2730 eprintln!("[G] Initial gas_backlog: {}", initial);
2731
2732 l2_pricing.update_pricing_model(1, 10).unwrap();
2734 let after_drain = l2_pricing.gas_backlog().unwrap();
2735 eprintln!("[G] After drain: gas_backlog={}", after_drain);
2736 assert_eq!(after_drain, 0);
2737
2738 state.merge_transitions(BundleRetention::Reverts);
2740 let mut bundle = state.take_bundle();
2741
2742 let pre_filter = bundle
2743 .state
2744 .get(&ARBOS_STATE_ADDRESS)
2745 .and_then(|a| a.storage.get(&gas_backlog_slot))
2746 .map(|s| (s.present_value, s.previous_or_original_value));
2747 eprintln!("[G] Bundle pre-filter: gas_backlog = {:?}", pre_filter);
2748
2749 for (_addr, account) in bundle.state.iter_mut() {
2751 account
2752 .storage
2753 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2754 }
2755
2756 let final_slot = bundle
2757 .state
2758 .get(&ARBOS_STATE_ADDRESS)
2759 .and_then(|a| a.storage.get(&gas_backlog_slot))
2760 .map(|s| (s.present_value, s.previous_or_original_value));
2761 eprintln!("[G] FINAL after filter: gas_backlog = {:?}", final_slot);
2762
2763 assert!(
2765 final_slot.is_some(),
2766 "VARIANT G FAILED: drain-to-0 write was lost!"
2767 );
2768 }
2769
2770 eprintln!("\n===== ALL VARIANTS PASSED =====");
2771 }
2772}