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], ()> {
310 let base_fee = self.base_fee_wei()?;
311 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
312 let mut fees = [U256::ZERO; NUM_RESOURCE_KIND];
313 for kind in ResourceKind::ALL {
314 if kind == ResourceKind::SingleDim {
315 fees[kind as usize] = base_fee;
316 continue;
317 }
318 let fee = mgf.get_current_block_fee(kind)?;
319 fees[kind as usize] = if fee.is_zero() { base_fee } else { fee };
320 }
321 Ok(fees)
322 }
323
324 pub fn get_current_multi_gas_fees(&self) -> Result<[U256; NUM_RESOURCE_KIND], ()> {
330 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
331 let mut fees = [U256::ZERO; NUM_RESOURCE_KIND];
332 for kind in ResourceKind::ALL {
333 if kind == ResourceKind::SingleDim {
334 continue;
335 }
336 fees[kind as usize] = mgf.get_current_block_fee(kind)?;
337 }
338 Ok(fees)
339 }
340
341 pub fn commit_multi_gas_fees(&self) -> Result<(), ()> {
345 if self.gas_model_to_use()? != GasModel::MultiGasConstraints {
346 return Ok(());
347 }
348 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
349 mgf.commit_next_to_current()
350 }
351
352 pub fn backlog_update_cost(&self) -> Result<u64, ()> {
360 use super::{STORAGE_READ_COST, STORAGE_WRITE_COST};
361
362 if self.arbos_version >= version::ARBOS_VERSION_60 {
364 return Ok(MULTI_CONSTRAINT_STATIC_BACKLOG_UPDATE_COST);
365 }
366
367 let mut result = 0u64;
368
369 if self.arbos_version >= version::ARBOS_VERSION_50 {
371 result += STORAGE_READ_COST;
372 }
373
374 if self.arbos_version >= version::ARBOS_VERSION_MULTI_CONSTRAINT_FIX {
376 let constraints_length = self.gas_constraints_length()?;
377 if constraints_length > 0 {
378 result += STORAGE_READ_COST;
380 result += constraints_length * (STORAGE_READ_COST + STORAGE_WRITE_COST);
382 return Ok(result);
383 }
384 }
386
387 result += STORAGE_READ_COST + STORAGE_WRITE_COST;
389
390 Ok(result)
391 }
392
393 pub fn set_gas_constraints_from_legacy(&self) -> Result<(), ()> {
395 self.clear_gas_constraints()?;
396 let target = self.speed_limit_per_second()?;
397 let adjustment_window = self.pricing_inertia()?;
398 let old_backlog = self.gas_backlog()?;
399 let backlog_tolerance = self.backlog_tolerance()?;
400
401 let backlog = old_backlog.saturating_sub(backlog_tolerance.saturating_mul(target));
402 self.add_gas_constraint(target, adjustment_window, backlog)
403 }
404
405 pub fn set_multi_gas_constraints_from_single_gas_constraints(&self) -> Result<(), ()> {
411 self.clear_multi_gas_constraints()?;
412
413 let length = self.gas_constraints_length()?;
414
415 for i in 0..length {
416 let c = self.open_gas_constraint_at(i);
417
418 let target = c.target()?;
419 let window = c.adjustment_window()?;
420 let backlog = c.backlog()?;
421
422 let weights = [1u64; NUM_RESOURCE_KIND];
424
425 let adjustment_window: u32 = if window > u32::MAX as u64 {
427 u32::MAX
428 } else {
429 window as u32
430 };
431
432 self.add_multi_gas_constraint(target, adjustment_window, backlog, &weights)?;
433 }
434 Ok(())
435 }
436
437 pub fn multi_dimensional_price_for_refund(&self, gas_used: MultiGas) -> Result<U256, ()> {
441 let fees = self.get_multi_gas_base_fee_per_resource()?;
442 let mut total = U256::ZERO;
443 for kind in ResourceKind::ALL {
444 let amount = gas_used.get(kind);
445 if amount == 0 {
446 continue;
447 }
448 total = total.saturating_add(U256::from(amount).saturating_mul(fees[kind as usize]));
449 }
450 Ok(total)
451 }
452
453 pub fn multi_dimensional_price_for_refund_with_fees(
459 &self,
460 gas_used: MultiGas,
461 cached_fees: &[U256; NUM_RESOURCE_KIND],
462 ) -> Result<U256, ()> {
463 let base_fee = self.base_fee_wei()?;
464 let mut total = U256::ZERO;
465 for kind in ResourceKind::ALL {
466 let amount = gas_used.get(kind);
467 if amount == 0 {
468 continue;
469 }
470 let fee = if kind == ResourceKind::SingleDim {
471 base_fee
472 } else {
473 let cached = cached_fees[kind as usize];
474 if cached.is_zero() {
475 base_fee
476 } else {
477 cached
478 }
479 };
480 total = total.saturating_add(U256::from(amount).saturating_mul(fee));
481 }
482 Ok(total)
483 }
484}
485
486fn approx_exp_basis_points(bips: u64) -> u64 {
490 const ACCURACY: u64 = 4;
491 const B: u64 = 10_000; if bips == 0 {
494 return B;
495 }
496
497 let mut res = B.saturating_add(bips / ACCURACY);
499 let mut i = ACCURACY - 1;
500 while i > 0 {
501 res = B.saturating_add(res.saturating_mul(bips) / (i * B));
502 i -= 1;
503 }
504
505 res
506}
507
508fn saturating_cast_to_i64(value: u64) -> i64 {
510 if value > i64::MAX as u64 {
511 i64::MAX
512 } else {
513 value as i64
514 }
515}
516
517fn natural_to_bips(natural: i64) -> i64 {
519 natural.saturating_mul(10000)
520}
521
522pub fn apply_gas_delta(backlog: u64, delta: i64) -> u64 {
524 if delta > 0 {
525 backlog.saturating_add(delta as u64)
526 } else {
527 backlog.saturating_sub((-delta) as u64)
528 }
529}
530
531fn apply_gas_delta_op(op: BacklogOperation, backlog: u64, delta: u64) -> u64 {
533 match op {
534 BacklogOperation::Grow => backlog.saturating_add(delta),
535 BacklogOperation::Shrink => backlog.saturating_sub(delta),
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use alloy_primitives::{address, keccak256, Address, B256, U256};
542 use arb_primitives::multigas::MultiGas;
543 use arb_storage::Storage;
544 use revm::{database::StateBuilder, Database};
545
546 const ARBOS_STATE_ADDRESS: Address = address!("A4B05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
547
548 #[derive(Default)]
549 struct EmptyDb;
550
551 impl Database for EmptyDb {
552 type Error = std::convert::Infallible;
553 fn basic(
554 &mut self,
555 _address: Address,
556 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
557 Ok(None)
558 }
559 fn code_by_hash(&mut self, _code_hash: B256) -> Result<revm::state::Bytecode, Self::Error> {
560 Ok(revm::state::Bytecode::default())
561 }
562 fn storage(&mut self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
563 Ok(U256::ZERO)
564 }
565 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
566 Ok(B256::ZERO)
567 }
568 }
569
570 fn ensure_cache_account(state: &mut revm::database::State<EmptyDb>, addr: Address) {
572 use revm::database::{states::account_status::AccountStatus, PlainAccount};
573
574 let _ = state.load_cache_account(addr);
575 if let Some(cached) = state.cache.accounts.get_mut(&addr) {
576 if cached.account.is_none() {
577 cached.account = Some(PlainAccount {
578 info: revm::state::AccountInfo {
579 balance: U256::ZERO,
580 nonce: 0,
581 code_hash: keccak256([]),
582 code: None,
583 account_id: None,
584 },
585 storage: Default::default(),
586 });
587 cached.status = AccountStatus::InMemoryChange;
588 }
589 }
590 }
591
592 #[test]
593 fn test_grow_backlog_through_l2_pricing_state() {
594 let mut state = StateBuilder::new()
595 .with_database(EmptyDb)
596 .with_bundle_update()
597 .build();
598
599 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
601 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
602
603 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
604
605 let backing = Storage::new(state_ptr, B256::ZERO);
607 let l2_sto = backing.open_sub_storage(&[1]);
608
609 super::super::initialize_l2_pricing_state(&l2_sto);
611
612 let l2_pricing = super::super::open_l2_pricing_state(
614 backing.open_sub_storage(&[1]),
615 10, );
617 let initial_backlog = l2_pricing.gas_backlog().unwrap();
618 assert_eq!(initial_backlog, 0, "Initial gasBacklog should be 0");
619
620 let result = l2_pricing.grow_backlog(100_000, MultiGas::default());
622 assert!(result.is_ok(), "grow_backlog should succeed");
623
624 let after_grow = l2_pricing.gas_backlog().unwrap();
626 assert_eq!(
627 after_grow, 100_000,
628 "gasBacklog should be 100000 after grow"
629 );
630
631 let result = l2_pricing.grow_backlog(50_000, MultiGas::default());
633 assert!(result.is_ok(), "second grow_backlog should succeed");
634
635 let after_second_grow = l2_pricing.gas_backlog().unwrap();
636 assert_eq!(
637 after_second_grow, 150_000,
638 "gasBacklog should be 150000 after second grow"
639 );
640
641 let result = l2_pricing.shrink_backlog(30_000, MultiGas::default());
643 assert!(result.is_ok(), "shrink_backlog should succeed");
644
645 let after_shrink = l2_pricing.gas_backlog().unwrap();
646 assert_eq!(
647 after_shrink, 120_000,
648 "gasBacklog should be 120000 after shrink"
649 );
650
651 use revm::database::states::bundle_state::BundleRetention;
653 state.merge_transitions(BundleRetention::Reverts);
654 let bundle = state.take_bundle();
655
656 let acct = bundle
657 .state
658 .get(&ARBOS_STATE_ADDRESS)
659 .expect("ArbOS account should be in bundle");
660
661 let l2_base = keccak256([1u8]); let gas_backlog_offset: u64 = 4;
665 let slot = arb_storage::storage_key_map(l2_base.as_slice(), gas_backlog_offset);
666
667 let bundle_slot = acct
668 .storage
669 .get(&slot)
670 .expect("gasBacklog slot should be in bundle");
671 assert_eq!(
672 bundle_slot.present_value,
673 U256::from(120_000u64),
674 "Bundle should contain final gasBacklog value"
675 );
676 }
677
678 #[test]
683 fn grow_backlog_survives_submit_retryable_then_retry_tx_flow() {
684 use alloy_primitives::map::HashMap;
685 use revm::{database::states::bundle_state::BundleRetention, DatabaseCommit};
686
687 let l2_base = keccak256([1u8]); let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
690 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
691 let per_block_gas_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 1);
692 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
693 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
694 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
695 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
696
697 let version_slot = arb_storage::storage_key_map(&[], 0);
699
700 struct PreloadedDb {
702 slots: HashMap<(Address, U256), U256>,
703 }
704
705 impl PreloadedDb {
706 fn new() -> Self {
707 Self {
708 slots: HashMap::default(),
709 }
710 }
711 fn set(&mut self, addr: Address, slot: U256, val: U256) {
712 self.slots.insert((addr, slot), val);
713 }
714 }
715
716 impl Database for PreloadedDb {
717 type Error = std::convert::Infallible;
718 fn basic(
719 &mut self,
720 addr: Address,
721 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
722 if addr == ARBOS_STATE_ADDRESS {
723 Ok(Some(revm::state::AccountInfo {
724 nonce: 1,
725 balance: U256::ZERO,
726 code_hash: keccak256([]),
727 code: None,
728 account_id: None,
729 }))
730 } else {
731 Ok(None)
732 }
733 }
734 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
735 Ok(revm::state::Bytecode::default())
736 }
737 fn storage(&mut self, addr: Address, index: U256) -> Result<U256, Self::Error> {
738 Ok(self
739 .slots
740 .get(&(addr, index))
741 .copied()
742 .unwrap_or(U256::ZERO))
743 }
744 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
745 Ok(B256::ZERO)
746 }
747 }
748
749 let arbos = ARBOS_STATE_ADDRESS;
750
751 let mut db = PreloadedDb::new();
753 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
754 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
755 db.set(arbos, per_block_gas_limit_slot, U256::from(32_000_000u64));
756 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
757 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
758 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
759 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
760 db.set(arbos, version_slot, U256::from(20u64)); let mut state = StateBuilder::new()
763 .with_database(db)
764 .with_bundle_update()
765 .build();
766
767 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
768
769 {
774 let backing = Storage::new(state_ptr, B256::ZERO);
775 let l2_sto = backing.open_sub_storage(&[1]);
776 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
777
778 let result = l2_pricing.update_pricing_model(0, 20);
780 assert!(result.is_ok(), "update_pricing_model should succeed");
781
782 let backlog = l2_pricing.gas_backlog().unwrap();
784 assert_eq!(
785 backlog, 552_756,
786 "gasBacklog should be 552756 after no-op drain"
787 );
788 }
789
790 let empty_changes: HashMap<Address, revm::state::Account> = Default::default();
792 state.commit(empty_changes);
793
794 {
799 let retryable_base = keccak256([2u8]); for i in 0u64..10 {
802 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
803 arb_storage::write_storage_at(
804 unsafe { &mut *state_ptr },
805 arbos,
806 slot,
807 U256::from(1000 + i),
808 );
809 }
810
811 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5); let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
814 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
815 arb_storage::write_storage_at(
816 unsafe { &mut *state_ptr },
817 arbos,
818 scratch_slot_1,
819 U256::from(42),
820 );
821 arb_storage::write_storage_at(
822 unsafe { &mut *state_ptr },
823 arbos,
824 scratch_slot_2,
825 U256::from(43),
826 );
827 arb_storage::write_storage_at(
828 unsafe { &mut *state_ptr },
829 arbos,
830 scratch_slot_3,
831 U256::from(44),
832 );
833 }
834
835 let empty_changes2: HashMap<Address, revm::state::Account> = Default::default();
837 state.commit(empty_changes2);
838
839 {
841 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
842 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
843 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
844 arb_storage::write_arbos_storage(
845 unsafe { &mut *state_ptr },
846 scratch_slot_1,
847 U256::ZERO,
848 );
849 arb_storage::write_arbos_storage(
850 unsafe { &mut *state_ptr },
851 scratch_slot_2,
852 U256::ZERO,
853 );
854 arb_storage::write_arbos_storage(
855 unsafe { &mut *state_ptr },
856 scratch_slot_3,
857 U256::ZERO,
858 );
859 }
860
861 {
867 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
868 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
869 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
870 arb_storage::write_storage_at(
871 unsafe { &mut *state_ptr },
872 arbos,
873 scratch_slot_1,
874 U256::from(99),
875 );
876 arb_storage::write_storage_at(
877 unsafe { &mut *state_ptr },
878 arbos,
879 scratch_slot_2,
880 U256::from(100),
881 );
882 arb_storage::write_storage_at(
883 unsafe { &mut *state_ptr },
884 arbos,
885 scratch_slot_3,
886 U256::from(101),
887 );
888 }
889
890 {
894 let mut evm_changes: HashMap<Address, revm::state::Account> = Default::default();
895
896 let sender = address!("fd86e9a33fd52e4085fb94d24b759448a621cd36");
898 let _ = state.load_cache_account(sender);
899 let mut sender_acct = revm::state::Account::default();
900 sender_acct.info.balance = U256::from(1_000_000_000u64);
901 sender_acct.info.nonce = 1;
902 sender_acct.mark_touch();
903 evm_changes.insert(sender, sender_acct);
904
905 let contracts = [
907 address!("4453d0eaf066a61c9b81ddc18bb5a2bf2fc52224"),
908 address!("7c7db13e5d385bcc797422d3c767856d15d24c5c"),
909 address!("0057892cb8bb5f1ce1b3c6f5ade899732249713f"),
910 address!("35aa95ac4747d928e2cd42fe4461f6d9d1826346"),
911 address!("e1e3b1cbacc870cb6e5f4bdf246feb6eb5cd351b"),
912 address!("7348fdf6f3e090c635b23d970945093455214f3b"),
913 address!("d50e4a971bc8ed55af6aebc0a2178456069e87b5"),
914 ];
915
916 for (i, &contract) in contracts.iter().enumerate() {
917 let _ = state.load_cache_account(contract);
918 let mut acct = revm::state::Account::default();
919 acct.info.nonce = 1;
920 acct.info.code_hash = keccak256(format!("code_{}", i).as_bytes());
921 acct.mark_touch();
922 for j in 0u64..3 {
924 let slot = U256::from(j);
925 let mut evm_slot =
926 revm::state::EvmStorageSlot::new(U256::from(i as u64 * 100 + j), 0);
927 evm_slot.present_value = U256::from(i as u64 * 100 + j + 1);
928 acct.storage.insert(slot, evm_slot);
929 }
930 evm_changes.insert(contract, acct);
931 }
932
933 state.commit(evm_changes);
934 }
935
936 {
938 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
939 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
940 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
941 arb_storage::write_arbos_storage(
942 unsafe { &mut *state_ptr },
943 scratch_slot_1,
944 U256::ZERO,
945 );
946 arb_storage::write_arbos_storage(
947 unsafe { &mut *state_ptr },
948 scratch_slot_2,
949 U256::ZERO,
950 );
951 arb_storage::write_arbos_storage(
952 unsafe { &mut *state_ptr },
953 scratch_slot_3,
954 U256::ZERO,
955 );
956 }
957
958 {
960 let retryable_base = keccak256([2u8]);
961 for i in 0u64..10 {
962 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
963 arb_storage::write_storage_at(unsafe { &mut *state_ptr }, arbos, slot, U256::ZERO);
964 }
965 }
966
967 {
969 let backing = Storage::new(state_ptr, B256::ZERO);
970 let l2_sto = backing.open_sub_storage(&[1]);
971 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
972
973 let backlog_before = l2_pricing.gas_backlog().unwrap();
974 assert_eq!(
975 backlog_before, 552_756,
976 "gasBacklog should still be 552756 before grow"
977 );
978
979 let result = l2_pricing.grow_backlog(357_751, MultiGas::default());
980 assert!(result.is_ok(), "grow_backlog should succeed");
981
982 let backlog_after = l2_pricing.gas_backlog().unwrap();
983 assert_eq!(
984 backlog_after, 910_507,
985 "gasBacklog should be 910507 after grow"
986 );
987 }
988
989 state.merge_transitions(BundleRetention::Reverts);
993 let mut bundle = state.take_bundle();
994
995 let pre_filter_backlog = bundle
997 .state
998 .get(&arbos)
999 .and_then(|a| a.storage.get(&gas_backlog_slot))
1000 .map(|s| s.present_value);
1001 assert_eq!(
1002 pre_filter_backlog,
1003 Some(U256::from(910_507u64)),
1004 "gasBacklog should be in bundle before filter with value 910507"
1005 );
1006
1007 for (_addr, account) in bundle.state.iter_mut() {
1009 account
1010 .storage
1011 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1012 }
1013
1014 let post_filter_backlog = bundle
1016 .state
1017 .get(&arbos)
1018 .and_then(|a| a.storage.get(&gas_backlog_slot))
1019 .map(|s| s.present_value);
1020 assert_eq!(
1021 post_filter_backlog,
1022 Some(U256::from(910_507u64)),
1023 "gasBacklog should survive filter_unchanged_storage with value 910507"
1024 );
1025
1026 let original = bundle
1028 .state
1029 .get(&arbos)
1030 .and_then(|a| a.storage.get(&gas_backlog_slot))
1031 .map(|s| s.previous_or_original_value);
1032 assert_eq!(
1033 original,
1034 Some(U256::from(552_756u64)),
1035 "gasBacklog original should be the pre-block DB value 552756"
1036 );
1037
1038 let cache_backlog = state
1042 .cache
1043 .accounts
1044 .get(&arbos)
1045 .and_then(|ca| ca.account.as_ref())
1046 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1047 assert_eq!(
1048 cache_backlog,
1049 Some(U256::from(910_507u64)),
1050 "gasBacklog should be in cache with value 910507"
1051 );
1052 }
1053
1054 #[test]
1057 fn grow_backlog_with_arbos_in_evm_commit() {
1058 use alloy_primitives::map::HashMap;
1059 use revm::{database::states::bundle_state::BundleRetention, DatabaseCommit};
1060
1061 let l2_base = keccak256([1u8]);
1062 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
1063 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
1064 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
1065 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
1066 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
1067 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
1068 let version_slot = arb_storage::storage_key_map(&[], 0);
1069 let scratch_1 = arb_storage::storage_key_map(&[], 5);
1071 let scratch_2 = arb_storage::storage_key_map(&[], 6);
1072
1073 struct PreloadedDb {
1074 slots: HashMap<(Address, U256), U256>,
1075 }
1076 impl PreloadedDb {
1077 fn new() -> Self {
1078 Self {
1079 slots: HashMap::default(),
1080 }
1081 }
1082 fn set(&mut self, addr: Address, slot: U256, val: U256) {
1083 self.slots.insert((addr, slot), val);
1084 }
1085 }
1086 impl Database for PreloadedDb {
1087 type Error = std::convert::Infallible;
1088 fn basic(
1089 &mut self,
1090 addr: Address,
1091 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
1092 if addr == ARBOS_STATE_ADDRESS {
1093 Ok(Some(revm::state::AccountInfo {
1094 nonce: 1,
1095 balance: U256::ZERO,
1096 code_hash: keccak256([]),
1097 code: None,
1098 account_id: None,
1099 }))
1100 } else {
1101 Ok(None)
1102 }
1103 }
1104 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
1105 Ok(revm::state::Bytecode::default())
1106 }
1107 fn storage(&mut self, addr: Address, index: U256) -> Result<U256, Self::Error> {
1108 Ok(self
1109 .slots
1110 .get(&(addr, index))
1111 .copied()
1112 .unwrap_or(U256::ZERO))
1113 }
1114 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
1115 Ok(B256::ZERO)
1116 }
1117 }
1118
1119 let arbos = ARBOS_STATE_ADDRESS;
1120 let mut db = PreloadedDb::new();
1121 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
1122 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
1123 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
1124 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
1125 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
1126 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
1127 db.set(arbos, version_slot, U256::from(20u64));
1128
1129 let mut state = StateBuilder::new()
1130 .with_database(db)
1131 .with_bundle_update()
1132 .build();
1133 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
1134
1135 {
1137 let backing = Storage::new(state_ptr, B256::ZERO);
1138 let l2_sto = backing.open_sub_storage(&[1]);
1139 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1140 let _ = l2_pricing.update_pricing_model(0, 20);
1141 }
1142 state.commit(HashMap::default());
1143
1144 {
1146 arb_storage::write_storage_at(
1147 unsafe { &mut *state_ptr },
1148 arbos,
1149 scratch_1,
1150 U256::from(42),
1151 );
1152 arb_storage::write_storage_at(
1153 unsafe { &mut *state_ptr },
1154 arbos,
1155 scratch_2,
1156 U256::from(43),
1157 );
1158 let retryable_base = keccak256([2u8]);
1159 for i in 0u64..5 {
1160 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
1161 arb_storage::write_storage_at(
1162 unsafe { &mut *state_ptr },
1163 arbos,
1164 slot,
1165 U256::from(1000 + i),
1166 );
1167 }
1168 }
1169 state.commit(HashMap::default());
1170 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_1, U256::ZERO);
1172 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_2, U256::ZERO);
1173
1174 arb_storage::write_storage_at(unsafe { &mut *state_ptr }, arbos, scratch_1, U256::from(99));
1176 arb_storage::write_storage_at(
1177 unsafe { &mut *state_ptr },
1178 arbos,
1179 scratch_2,
1180 U256::from(100),
1181 );
1182
1183 {
1185 let mut evm_changes: HashMap<Address, revm::state::Account> = Default::default();
1186
1187 let sender = address!("fd86e9a33fd52e4085fb94d24b759448a621cd36");
1189 let _ = state.load_cache_account(sender);
1190 let mut sender_acct = revm::state::Account::default();
1191 sender_acct.info.balance = U256::from(1_000_000_000u64);
1192 sender_acct.info.nonce = 1;
1193 sender_acct.mark_touch();
1194 evm_changes.insert(sender, sender_acct);
1195
1196 let _ = state.load_cache_account(arbos);
1199 let mut arbos_acct = revm::state::Account {
1200 info: revm::state::AccountInfo {
1201 nonce: 1,
1202 balance: U256::ZERO,
1203 code_hash: keccak256([]),
1204 code: None,
1205 account_id: None,
1206 },
1207 ..Default::default()
1208 };
1209 arbos_acct.storage.insert(
1212 scratch_1,
1213 revm::state::EvmStorageSlot::new(U256::from(99), 0),
1214 );
1215 arbos_acct.mark_touch();
1216 evm_changes.insert(arbos, arbos_acct);
1217
1218 state.commit(evm_changes);
1219 }
1220
1221 let _backlog_check =
1223 arb_storage::read_storage_at(unsafe { &mut *state_ptr }, arbos, gas_backlog_slot);
1224
1225 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_1, U256::ZERO);
1227 arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_2, U256::ZERO);
1228
1229 {
1231 let retryable_base = keccak256([2u8]);
1232 for i in 0u64..5 {
1233 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
1234 arb_storage::write_storage_at(unsafe { &mut *state_ptr }, arbos, slot, U256::ZERO);
1235 }
1236 }
1237
1238 {
1240 let backing = Storage::new(state_ptr, B256::ZERO);
1241 let l2_sto = backing.open_sub_storage(&[1]);
1242 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1243
1244 let backlog_before = l2_pricing.gas_backlog().unwrap();
1245 assert_eq!(
1246 backlog_before, 552_756,
1247 "gasBacklog should be 552756 before grow"
1248 );
1249
1250 let _ = l2_pricing.grow_backlog(357_751, MultiGas::default());
1251
1252 let backlog_after = l2_pricing.gas_backlog().unwrap();
1253 assert_eq!(backlog_after, 910_507, "gasBacklog should be 910507");
1254 }
1255
1256 state.merge_transitions(BundleRetention::Reverts);
1258 let mut bundle = state.take_bundle();
1259
1260 let pre_filter = bundle
1261 .state
1262 .get(&arbos)
1263 .and_then(|a| a.storage.get(&gas_backlog_slot))
1264 .map(|s| (s.present_value, s.previous_or_original_value));
1265 assert_eq!(
1266 pre_filter.map(|p| p.0),
1267 Some(U256::from(910_507u64)),
1268 "gasBacklog should be 910507 in bundle before filter"
1269 );
1270
1271 for (_addr, account) in bundle.state.iter_mut() {
1273 account
1274 .storage
1275 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1276 }
1277
1278 let post_filter = bundle
1279 .state
1280 .get(&arbos)
1281 .and_then(|a| a.storage.get(&gas_backlog_slot))
1282 .map(|s| s.present_value);
1283 assert_eq!(
1284 post_filter,
1285 Some(U256::from(910_507u64)),
1286 "gasBacklog should survive filter when ArbOS is in EVM commit"
1287 );
1288 }
1289
1290 #[test]
1294 fn grow_backlog_with_transition_state_consumed() {
1295 use alloy_primitives::map::HashMap;
1296 use revm::{
1297 database::states::{bundle_state::BundleRetention, plain_account::StorageSlot},
1298 DatabaseCommit,
1299 };
1300
1301 let l2_base = keccak256([1u8]);
1302 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
1303 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
1304 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
1305 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
1306 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
1307 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
1308 let version_slot = arb_storage::storage_key_map(&[], 0);
1309
1310 struct PreloadedDb(HashMap<(Address, U256), U256>);
1311 impl PreloadedDb {
1312 fn new() -> Self {
1313 Self(HashMap::default())
1314 }
1315 fn set(&mut self, a: Address, s: U256, v: U256) {
1316 self.0.insert((a, s), v);
1317 }
1318 }
1319 impl Database for PreloadedDb {
1320 type Error = std::convert::Infallible;
1321 fn basic(
1322 &mut self,
1323 addr: Address,
1324 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
1325 if addr == ARBOS_STATE_ADDRESS {
1326 Ok(Some(revm::state::AccountInfo {
1327 nonce: 1,
1328 balance: U256::ZERO,
1329 code_hash: keccak256([]),
1330 code: None,
1331 account_id: None,
1332 }))
1333 } else {
1334 Ok(None)
1335 }
1336 }
1337 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
1338 Ok(revm::state::Bytecode::default())
1339 }
1340 fn storage(&mut self, a: Address, i: U256) -> Result<U256, Self::Error> {
1341 Ok(self.0.get(&(a, i)).copied().unwrap_or(U256::ZERO))
1342 }
1343 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
1344 Ok(B256::ZERO)
1345 }
1346 }
1347
1348 let arbos = ARBOS_STATE_ADDRESS;
1349 let mut db = PreloadedDb::new();
1350 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
1351 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
1352 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
1353 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
1354 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
1355 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
1356 db.set(arbos, version_slot, U256::from(20u64));
1357
1358 let mut state = StateBuilder::new()
1359 .with_database(db)
1360 .with_bundle_update()
1361 .build();
1362 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
1363
1364 {
1366 let backing = Storage::new(state_ptr, B256::ZERO);
1367 let l2_sto = backing.open_sub_storage(&[1]);
1368 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1369 let _ = l2_pricing.update_pricing_model(0, 20);
1370 }
1371 state.commit(HashMap::default());
1372
1373 state.merge_transitions(BundleRetention::Reverts);
1378 let _mid_bundle = state.take_bundle();
1379
1380 let _ts_is_none = state.transition_state.is_none();
1382
1383 {
1385 let backing = Storage::new(state_ptr, B256::ZERO);
1386 let l2_sto = backing.open_sub_storage(&[1]);
1387 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1388
1389 let _backlog_before = l2_pricing.gas_backlog().unwrap();
1390
1391 let _ = l2_pricing.grow_backlog(357_751, MultiGas::default());
1392
1393 let _backlog_after = l2_pricing.gas_backlog().unwrap();
1394 }
1395
1396 state.merge_transitions(BundleRetention::Reverts);
1398 let mut bundle = state.take_bundle();
1399
1400 let _in_bundle = bundle
1402 .state
1403 .get(&arbos)
1404 .and_then(|a| a.storage.get(&gas_backlog_slot))
1405 .map(|s| s.present_value);
1406
1407 {
1411 let _cache_val = state
1412 .cache
1413 .accounts
1414 .get(&arbos)
1415 .and_then(|ca| ca.account.as_ref())
1416 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1417
1418 if let Some(bundle_acct) = bundle.state.get_mut(&arbos) {
1420 if let Some(cached_acc) = state.cache.accounts.get(&arbos) {
1421 if let Some(ref plain) = cached_acc.account {
1422 for (key, value) in &plain.storage {
1423 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1424 slot.present_value = *value;
1425 } else {
1426 let original =
1427 state.database.storage(arbos, *key).unwrap_or(U256::ZERO);
1428 if *value != original {
1429 bundle_acct.storage.insert(
1430 *key,
1431 StorageSlot {
1432 previous_or_original_value: original,
1433 present_value: *value,
1434 },
1435 );
1436 }
1437 }
1438 }
1439 }
1440 }
1441 } else {
1442 if let Some(cached_acc) = state.cache.accounts.get(&arbos) {
1444 if let Some(ref plain) = cached_acc.account {
1445 let mut storage_changes: HashMap<U256, StorageSlot> = HashMap::default();
1446 for (key, value) in &plain.storage {
1447 let original =
1448 state.database.storage(arbos, *key).unwrap_or(U256::ZERO);
1449 if *value != original {
1450 storage_changes.insert(
1451 *key,
1452 StorageSlot {
1453 previous_or_original_value: original,
1454 present_value: *value,
1455 },
1456 );
1457 }
1458 }
1459 if !storage_changes.is_empty() {
1460 bundle.state.insert(
1461 arbos,
1462 revm::database::BundleAccount {
1463 info: Some(plain.info.clone()),
1464 original_info: None,
1465 storage: storage_changes,
1466 status: revm::database::AccountStatus::Changed,
1467 },
1468 );
1469 }
1470 }
1471 }
1472 }
1473 }
1474
1475 let _after_augment = bundle
1476 .state
1477 .get(&arbos)
1478 .and_then(|a| a.storage.get(&gas_backlog_slot))
1479 .map(|s| s.present_value);
1480
1481 for (_addr, account) in bundle.state.iter_mut() {
1483 account
1484 .storage
1485 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1486 }
1487
1488 let after_filter = bundle
1489 .state
1490 .get(&arbos)
1491 .and_then(|a| a.storage.get(&gas_backlog_slot))
1492 .map(|s| s.present_value);
1493
1494 assert_eq!(
1495 after_filter,
1496 Some(U256::from(910_507u64)),
1497 "gasBacklog MUST survive even when transition_state was consumed mid-block"
1498 );
1499 }
1500
1501 #[test]
1509 fn test_grow_backlog_survives_evm_commit_and_augment() {
1510 use revm::{
1511 database::states::{bundle_state::BundleRetention, plain_account::StorageSlot},
1512 DatabaseCommit,
1513 };
1514
1515 let l2_base = keccak256([1u8]); let gas_backlog_offset: u64 = 4;
1518 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), gas_backlog_offset);
1519
1520 {
1522 let mut state = StateBuilder::new()
1523 .with_database(EmptyDb)
1524 .with_bundle_update()
1525 .build();
1526
1527 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1529 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1530
1531 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1532
1533 let backing = Storage::new(state_ptr, B256::ZERO);
1535 let l2_sto = backing.open_sub_storage(&[1]);
1536 super::super::initialize_l2_pricing_state(&l2_sto);
1537
1538 let l2_pricing =
1540 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1541 l2_pricing.set_gas_backlog(552756).unwrap();
1542 let pre_start = l2_pricing.gas_backlog().unwrap();
1543 assert_eq!(pre_start, 552756, "Pre-existing backlog should be 552756");
1544
1545 l2_pricing.update_pricing_model(0, 10).unwrap();
1547 let after_start = l2_pricing.gas_backlog().unwrap();
1548 assert_eq!(
1549 after_start, 552756,
1550 "time_passed=0 should not change backlog"
1551 );
1552
1553 let empty_state: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1555 Default::default();
1556 state.commit(empty_state);
1557
1558 let _cache_val = state
1560 .cache
1561 .accounts
1562 .get(&ARBOS_STATE_ADDRESS)
1563 .and_then(|ca| ca.account.as_ref())
1564 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1565
1566 let l2_pricing2 =
1568 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1569 l2_pricing2
1570 .grow_backlog(357751, MultiGas::default())
1571 .unwrap();
1572 let after_grow = l2_pricing2.gas_backlog().unwrap();
1573 assert_eq!(after_grow, 552756 + 357751, "backlog should be sum");
1574
1575 let _cache_val2 = state
1577 .cache
1578 .accounts
1579 .get(&ARBOS_STATE_ADDRESS)
1580 .and_then(|ca| ca.account.as_ref())
1581 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1582
1583 state.merge_transitions(BundleRetention::Reverts);
1585 let mut bundle = state.take_bundle();
1586
1587 let _bundle_has_slot = bundle
1589 .state
1590 .get(&ARBOS_STATE_ADDRESS)
1591 .and_then(|a| a.storage.get(&gas_backlog_slot))
1592 .map(|s| s.present_value);
1593
1594 for (addr, cache_acct) in &state.cache.accounts {
1597 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1598 let current_storage = cache_acct
1599 .account
1600 .as_ref()
1601 .map(|a| &a.storage)
1602 .cloned()
1603 .unwrap_or_default();
1604
1605 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1606 bundle_acct.info = current_info;
1607 for (key, value) in ¤t_storage {
1608 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1609 slot.present_value = *value;
1610 } else {
1611 let original_value = U256::ZERO;
1614 if *value != original_value {
1615 bundle_acct.storage.insert(
1616 *key,
1617 StorageSlot {
1618 previous_or_original_value: original_value,
1619 present_value: *value,
1620 },
1621 );
1622 }
1623 }
1624 }
1625 } else {
1626 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
1628 current_storage
1629 .iter()
1630 .filter_map(|(key, value)| {
1631 let original_value = U256::ZERO;
1632 if original_value != *value {
1633 Some((
1634 *key,
1635 StorageSlot {
1636 previous_or_original_value: original_value,
1637 present_value: *value,
1638 },
1639 ))
1640 } else {
1641 None
1642 }
1643 })
1644 .collect();
1645
1646 let info_changed = current_info.is_some(); if info_changed || !storage_changes.is_empty() {
1648 bundle.state.insert(
1649 *addr,
1650 revm::database::BundleAccount {
1651 info: current_info,
1652 original_info: None,
1653 storage: storage_changes,
1654 status: revm::database::AccountStatus::InMemoryChange,
1655 },
1656 );
1657 }
1658 }
1659 }
1660
1661 let _bundle_after_augment = bundle
1662 .state
1663 .get(&ARBOS_STATE_ADDRESS)
1664 .and_then(|a| a.storage.get(&gas_backlog_slot))
1665 .map(|s| (s.present_value, s.previous_or_original_value));
1666
1667 for (_addr, account) in bundle.state.iter_mut() {
1669 account
1670 .storage
1671 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1672 }
1673
1674 let final_slot = bundle
1675 .state
1676 .get(&ARBOS_STATE_ADDRESS)
1677 .and_then(|a| a.storage.get(&gas_backlog_slot))
1678 .map(|s| s.present_value);
1679 assert!(
1680 final_slot.is_some(),
1681 "VARIANT A FAILED: gas_backlog slot MISSING from bundle after empty EVM commit"
1682 );
1683 assert_eq!(
1684 final_slot.unwrap(),
1685 U256::from(552756u64 + 357751u64),
1686 "VARIANT A: gas_backlog should be 910507"
1687 );
1688 }
1689
1690 {
1692 let mut state = StateBuilder::new()
1693 .with_database(EmptyDb)
1694 .with_bundle_update()
1695 .build();
1696
1697 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1698 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1699
1700 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1701
1702 let backing = Storage::new(state_ptr, B256::ZERO);
1703 let l2_sto = backing.open_sub_storage(&[1]);
1704 super::super::initialize_l2_pricing_state(&l2_sto);
1705
1706 let l2_pricing =
1707 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1708 l2_pricing.set_gas_backlog(552756).unwrap();
1709 l2_pricing.update_pricing_model(0, 10).unwrap();
1710 let _before_commit = l2_pricing.gas_backlog().unwrap();
1711
1712 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
1717 let mut arbos_evm_account = revm::state::Account {
1718 info: revm::state::AccountInfo {
1719 balance: U256::ZERO,
1720 nonce: 1,
1721 code_hash: keccak256([]),
1722 code: None,
1723 account_id: None,
1724 },
1725 ..Default::default()
1726 };
1727 arbos_evm_account.mark_touch();
1728 let mut evm_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1730 Default::default();
1731 evm_changes.insert(ARBOS_STATE_ADDRESS, arbos_evm_account);
1732 state.commit(evm_changes);
1733
1734 let l2_pricing2 =
1736 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1737 let _read_before_grow = l2_pricing2.gas_backlog().unwrap();
1738
1739 l2_pricing2
1740 .grow_backlog(357751, MultiGas::default())
1741 .unwrap();
1742 let _after_grow = l2_pricing2.gas_backlog().unwrap();
1743
1744 let _cache_val2 = state
1746 .cache
1747 .accounts
1748 .get(&ARBOS_STATE_ADDRESS)
1749 .and_then(|ca| ca.account.as_ref())
1750 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1751
1752 state.merge_transitions(BundleRetention::Reverts);
1754 let mut bundle = state.take_bundle();
1755
1756 let _bundle_pre = bundle
1757 .state
1758 .get(&ARBOS_STATE_ADDRESS)
1759 .and_then(|a| a.storage.get(&gas_backlog_slot))
1760 .map(|s| (s.present_value, s.previous_or_original_value));
1761
1762 for (addr, cache_acct) in &state.cache.accounts {
1764 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1765 let current_storage = cache_acct
1766 .account
1767 .as_ref()
1768 .map(|a| &a.storage)
1769 .cloned()
1770 .unwrap_or_default();
1771
1772 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1773 bundle_acct.info = current_info;
1774 for (key, value) in ¤t_storage {
1775 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1776 slot.present_value = *value;
1777 } else {
1778 let original_value = U256::ZERO;
1779 if *value != original_value {
1780 bundle_acct.storage.insert(
1781 *key,
1782 StorageSlot {
1783 previous_or_original_value: original_value,
1784 present_value: *value,
1785 },
1786 );
1787 }
1788 }
1789 }
1790 }
1791 }
1792
1793 let _bundle_post = bundle
1794 .state
1795 .get(&ARBOS_STATE_ADDRESS)
1796 .and_then(|a| a.storage.get(&gas_backlog_slot))
1797 .map(|s| (s.present_value, s.previous_or_original_value));
1798
1799 for (_addr, account) in bundle.state.iter_mut() {
1801 account
1802 .storage
1803 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1804 }
1805
1806 let final_slot = bundle
1807 .state
1808 .get(&ARBOS_STATE_ADDRESS)
1809 .and_then(|a| a.storage.get(&gas_backlog_slot))
1810 .map(|s| s.present_value);
1811 assert!(
1812 final_slot.is_some(),
1813 "VARIANT B FAILED: gas_backlog slot MISSING from bundle after EVM commit with ArbOS touched"
1814 );
1815 assert_eq!(
1816 final_slot.unwrap(),
1817 U256::from(552756u64 + 357751u64),
1818 "VARIANT B: gas_backlog should be 910507"
1819 );
1820 }
1821
1822 {
1827 let mut state = StateBuilder::new()
1828 .with_database(EmptyDb)
1829 .with_bundle_update()
1830 .build();
1831
1832 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1833 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1834
1835 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1836
1837 let backing = Storage::new(state_ptr, B256::ZERO);
1838 let l2_sto = backing.open_sub_storage(&[1]);
1839 super::super::initialize_l2_pricing_state(&l2_sto);
1840
1841 let l2_pricing =
1842 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1843 l2_pricing.set_gas_backlog(552756).unwrap();
1844 l2_pricing.update_pricing_model(0, 10).unwrap();
1845
1846 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
1849 let mut arbos_evm_account = revm::state::Account {
1850 info: revm::state::AccountInfo {
1851 balance: U256::ZERO,
1852 nonce: 1,
1853 code_hash: keccak256([]),
1854 code: None,
1855 account_id: None,
1856 },
1857 ..Default::default()
1858 };
1859 arbos_evm_account.mark_touch();
1860
1861 arbos_evm_account.storage.insert(
1864 gas_backlog_slot,
1865 revm::state::EvmStorageSlot::new(U256::from(552756u64), 0),
1866 );
1868
1869 let mut evm_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1870 Default::default();
1871 evm_changes.insert(ARBOS_STATE_ADDRESS, arbos_evm_account);
1872 state.commit(evm_changes);
1873
1874 let l2_pricing2 =
1876 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1877 let _read_before_grow = l2_pricing2.gas_backlog().unwrap();
1878
1879 l2_pricing2
1880 .grow_backlog(357751, MultiGas::default())
1881 .unwrap();
1882 let _after_grow = l2_pricing2.gas_backlog().unwrap();
1883
1884 state.merge_transitions(BundleRetention::Reverts);
1886 let mut bundle = state.take_bundle();
1887
1888 let _bundle_pre = bundle
1889 .state
1890 .get(&ARBOS_STATE_ADDRESS)
1891 .and_then(|a| a.storage.get(&gas_backlog_slot))
1892 .map(|s| (s.present_value, s.previous_or_original_value));
1893
1894 for (addr, cache_acct) in &state.cache.accounts {
1896 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1897 let current_storage = cache_acct
1898 .account
1899 .as_ref()
1900 .map(|a| &a.storage)
1901 .cloned()
1902 .unwrap_or_default();
1903
1904 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1905 bundle_acct.info = current_info;
1906 for (key, value) in ¤t_storage {
1907 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1908 slot.present_value = *value;
1909 } else {
1910 let original_value = U256::ZERO;
1911 if *value != original_value {
1912 bundle_acct.storage.insert(
1913 *key,
1914 StorageSlot {
1915 previous_or_original_value: original_value,
1916 present_value: *value,
1917 },
1918 );
1919 }
1920 }
1921 }
1922 }
1923 }
1924
1925 let _bundle_post = bundle
1926 .state
1927 .get(&ARBOS_STATE_ADDRESS)
1928 .and_then(|a| a.storage.get(&gas_backlog_slot))
1929 .map(|s| (s.present_value, s.previous_or_original_value));
1930
1931 for (_addr, account) in bundle.state.iter_mut() {
1933 account
1934 .storage
1935 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1936 }
1937
1938 let final_slot = bundle
1939 .state
1940 .get(&ARBOS_STATE_ADDRESS)
1941 .and_then(|a| a.storage.get(&gas_backlog_slot))
1942 .map(|s| s.present_value);
1943 assert!(
1944 final_slot.is_some(),
1945 "VARIANT C FAILED: gas_backlog slot MISSING from bundle after EVM commit with ArbOS storage read"
1946 );
1947 assert_eq!(
1948 final_slot.unwrap(),
1949 U256::from(552756u64 + 357751u64),
1950 "VARIANT C: gas_backlog should be 910507"
1951 );
1952 }
1953
1954 {
1957 let mut state = StateBuilder::new()
1958 .with_database(EmptyDb)
1959 .with_bundle_update()
1960 .build();
1961
1962 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1963 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1964
1965 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1966
1967 let backing = Storage::new(state_ptr, B256::ZERO);
1968 let l2_sto = backing.open_sub_storage(&[1]);
1969 super::super::initialize_l2_pricing_state(&l2_sto);
1970
1971 let l2_pricing =
1973 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1974 l2_pricing.set_gas_backlog(552756).unwrap();
1975
1976 l2_pricing.update_pricing_model(0, 10).unwrap();
1978
1979 state.commit(Default::default());
1981
1982 let sender = address!("1111111111111111111111111111111111111111");
1984 let receiver = address!("2222222222222222222222222222222222222222");
1985 let _ = state.load_cache_account(sender);
1986 let _ = state.load_cache_account(receiver);
1987
1988 let mut user_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1989 Default::default();
1990 let mut sender_acct = revm::state::Account::default();
1991 sender_acct.info.balance = U256::from(999_000u64);
1992 sender_acct.info.nonce = 1;
1993 sender_acct.mark_touch();
1994 user_changes.insert(sender, sender_acct);
1995
1996 let mut receiver_acct = revm::state::Account::default();
1997 receiver_acct.info.balance = U256::from(1_000u64);
1998 receiver_acct.mark_touch();
1999 user_changes.insert(receiver, receiver_acct);
2000
2001 state.commit(user_changes);
2002
2003 let _cache_val_after_user = state
2005 .cache
2006 .accounts
2007 .get(&ARBOS_STATE_ADDRESS)
2008 .and_then(|ca| ca.account.as_ref())
2009 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
2010
2011 let l2_pricing2 =
2014 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2015 let _read_val = l2_pricing2.gas_backlog().unwrap();
2016 l2_pricing2
2017 .grow_backlog(357751, MultiGas::default())
2018 .unwrap();
2019 let after_grow = l2_pricing2.gas_backlog().unwrap();
2020 assert_eq!(after_grow, 552756 + 357751, "backlog should be sum");
2021
2022 state.merge_transitions(BundleRetention::Reverts);
2024 let mut bundle = state.take_bundle();
2025
2026 let _bundle_pre = bundle
2027 .state
2028 .get(&ARBOS_STATE_ADDRESS)
2029 .and_then(|a| a.storage.get(&gas_backlog_slot))
2030 .map(|s| (s.present_value, s.previous_or_original_value));
2031
2032 for (addr, cache_acct) in &state.cache.accounts {
2034 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2035 let current_storage = cache_acct
2036 .account
2037 .as_ref()
2038 .map(|a| &a.storage)
2039 .cloned()
2040 .unwrap_or_default();
2041
2042 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2043 bundle_acct.info = current_info;
2044 for (key, value) in ¤t_storage {
2045 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2046 slot.present_value = *value;
2047 } else {
2048 let original_value = U256::ZERO;
2049 if *value != original_value {
2050 bundle_acct.storage.insert(
2051 *key,
2052 StorageSlot {
2053 previous_or_original_value: original_value,
2054 present_value: *value,
2055 },
2056 );
2057 }
2058 }
2059 }
2060 } else {
2061 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
2062 current_storage
2063 .iter()
2064 .filter_map(|(key, value)| {
2065 let original_value = U256::ZERO;
2066 if original_value != *value {
2067 Some((
2068 *key,
2069 StorageSlot {
2070 previous_or_original_value: original_value,
2071 present_value: *value,
2072 },
2073 ))
2074 } else {
2075 None
2076 }
2077 })
2078 .collect();
2079 let info_changed = current_info.is_some();
2080 if info_changed || !storage_changes.is_empty() {
2081 bundle.state.insert(
2082 *addr,
2083 revm::database::BundleAccount {
2084 info: current_info,
2085 original_info: None,
2086 storage: storage_changes,
2087 status: revm::database::AccountStatus::InMemoryChange,
2088 },
2089 );
2090 }
2091 }
2092 }
2093
2094 let _bundle_post = bundle
2095 .state
2096 .get(&ARBOS_STATE_ADDRESS)
2097 .and_then(|a| a.storage.get(&gas_backlog_slot))
2098 .map(|s| (s.present_value, s.previous_or_original_value));
2099
2100 for (_addr, account) in bundle.state.iter_mut() {
2102 account
2103 .storage
2104 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2105 }
2106
2107 let final_slot = bundle
2108 .state
2109 .get(&ARBOS_STATE_ADDRESS)
2110 .and_then(|a| a.storage.get(&gas_backlog_slot))
2111 .map(|s| s.present_value);
2112 assert!(
2113 final_slot.is_some(),
2114 "VARIANT D FAILED: gas_backlog slot MISSING from bundle"
2115 );
2116 assert_eq!(
2117 final_slot.unwrap(),
2118 U256::from(552756u64 + 357751u64),
2119 "VARIANT D: gas_backlog should be 910507"
2120 );
2121 }
2122
2123 {
2129 struct PrePopulatedDb {
2131 gas_backlog_slot: U256,
2132 pre_existing_backlog: U256,
2133 }
2134
2135 impl Database for PrePopulatedDb {
2136 type Error = std::convert::Infallible;
2137 fn basic(
2138 &mut self,
2139 _address: Address,
2140 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2141 Ok(Some(revm::state::AccountInfo {
2143 balance: U256::ZERO,
2144 nonce: 1,
2145 code_hash: keccak256([]),
2146 code: None,
2147 account_id: None,
2148 }))
2149 }
2150 fn code_by_hash(
2151 &mut self,
2152 _code_hash: B256,
2153 ) -> Result<revm::state::Bytecode, Self::Error> {
2154 Ok(revm::state::Bytecode::default())
2155 }
2156 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2157 if index == self.gas_backlog_slot {
2159 Ok(self.pre_existing_backlog)
2160 } else {
2161 Ok(U256::ZERO)
2162 }
2163 }
2164 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2165 Ok(B256::ZERO)
2166 }
2167 }
2168
2169 let pre_existing_backlog = U256::from(552756u64);
2170 let mut state = StateBuilder::new()
2171 .with_database(PrePopulatedDb {
2172 gas_backlog_slot,
2173 pre_existing_backlog,
2174 })
2175 .with_bundle_update()
2176 .build();
2177
2178 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2180
2181 let state_ptr: *mut revm::database::State<PrePopulatedDb> = &mut state;
2182
2183 let backing = Storage::new(state_ptr, B256::ZERO);
2185 let l2_pricing =
2186 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2187
2188 let current = l2_pricing.gas_backlog().unwrap();
2190 assert_eq!(current, 552756, "Should read from DB");
2191
2192 l2_pricing.update_pricing_model(0, 10).unwrap();
2194 let _after_start = l2_pricing.gas_backlog().unwrap();
2195
2196 {
2198 use revm::DatabaseCommit;
2199 let empty: alloy_primitives::map::HashMap<Address, revm::state::Account> =
2200 Default::default();
2201 state.commit(empty);
2202 }
2203
2204 {
2206 use revm::DatabaseCommit;
2207 let sender = address!("1111111111111111111111111111111111111111");
2208 let _ = state.load_cache_account(sender);
2209 let mut user_changes: alloy_primitives::map::HashMap<
2210 Address,
2211 revm::state::Account,
2212 > = Default::default();
2213 let mut sender_acct = revm::state::Account::default();
2214 sender_acct.info.balance = U256::from(999_000u64);
2215 sender_acct.info.nonce = 1;
2216 sender_acct.mark_touch();
2217 user_changes.insert(sender, sender_acct);
2218 state.commit(user_changes);
2219 }
2220
2221 let l2_pricing2 =
2223 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2224 let _read_before = l2_pricing2.gas_backlog().unwrap();
2225
2226 l2_pricing2
2227 .grow_backlog(357751, MultiGas::default())
2228 .unwrap();
2229 let _after_grow = l2_pricing2.gas_backlog().unwrap();
2230
2231 let _cache_val = state
2233 .cache
2234 .accounts
2235 .get(&ARBOS_STATE_ADDRESS)
2236 .and_then(|ca| ca.account.as_ref())
2237 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
2238
2239 state.merge_transitions(BundleRetention::Reverts);
2241 let mut bundle = state.take_bundle();
2242
2243 let _bundle_pre = bundle
2244 .state
2245 .get(&ARBOS_STATE_ADDRESS)
2246 .and_then(|a| a.storage.get(&gas_backlog_slot))
2247 .map(|s| (s.present_value, s.previous_or_original_value));
2248
2249 for (addr, cache_acct) in &state.cache.accounts {
2251 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2252 let current_storage = cache_acct
2253 .account
2254 .as_ref()
2255 .map(|a| &a.storage)
2256 .cloned()
2257 .unwrap_or_default();
2258
2259 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2260 bundle_acct.info = current_info;
2261 for (key, value) in ¤t_storage {
2262 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2263 slot.present_value = *value;
2264 } else {
2265 let original_value =
2267 if *addr == ARBOS_STATE_ADDRESS && *key == gas_backlog_slot {
2268 pre_existing_backlog
2269 } else {
2270 U256::ZERO
2271 };
2272 if *value != original_value {
2273 bundle_acct.storage.insert(
2274 *key,
2275 StorageSlot {
2276 previous_or_original_value: original_value,
2277 present_value: *value,
2278 },
2279 );
2280 }
2281 }
2282 }
2283 } else {
2284 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
2286 current_storage
2287 .iter()
2288 .filter_map(|(key, value)| {
2289 let original_value =
2290 if *addr == ARBOS_STATE_ADDRESS && *key == gas_backlog_slot {
2291 pre_existing_backlog
2292 } else {
2293 U256::ZERO
2294 };
2295 if original_value != *value {
2296 Some((
2297 *key,
2298 StorageSlot {
2299 previous_or_original_value: original_value,
2300 present_value: *value,
2301 },
2302 ))
2303 } else {
2304 None
2305 }
2306 })
2307 .collect();
2308 let info_changed = false; if info_changed || !storage_changes.is_empty() {
2310 bundle.state.insert(
2311 *addr,
2312 revm::database::BundleAccount {
2313 info: current_info,
2314 original_info: None,
2315 storage: storage_changes,
2316 status: revm::database::AccountStatus::Changed,
2317 },
2318 );
2319 }
2320 }
2321 }
2322
2323 let _bundle_post = bundle
2324 .state
2325 .get(&ARBOS_STATE_ADDRESS)
2326 .and_then(|a| a.storage.get(&gas_backlog_slot))
2327 .map(|s| (s.present_value, s.previous_or_original_value));
2328
2329 for (_addr, account) in bundle.state.iter_mut() {
2331 account
2332 .storage
2333 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2334 }
2335
2336 let final_slot = bundle
2337 .state
2338 .get(&ARBOS_STATE_ADDRESS)
2339 .and_then(|a| a.storage.get(&gas_backlog_slot))
2340 .map(|s| s.present_value);
2341 assert!(
2342 final_slot.is_some(),
2343 "VARIANT E FAILED: gas_backlog slot MISSING from bundle (pre-populated DB)"
2344 );
2345 assert_eq!(
2346 final_slot.unwrap(),
2347 U256::from(552756u64 + 357751u64),
2348 "VARIANT E: gas_backlog should be 910507"
2349 );
2350 }
2351
2352 {
2362 struct PrePopulatedDb2 {
2363 gas_backlog_slot: U256,
2364 pre_existing_backlog: U256,
2365 speed_limit_slot: U256,
2366 speed_limit_value: U256,
2367 }
2368
2369 impl Database for PrePopulatedDb2 {
2370 type Error = std::convert::Infallible;
2371 fn basic(
2372 &mut self,
2373 _address: Address,
2374 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2375 Ok(Some(revm::state::AccountInfo {
2376 balance: U256::ZERO,
2377 nonce: 1,
2378 code_hash: keccak256([]),
2379 code: None,
2380 account_id: None,
2381 }))
2382 }
2383 fn code_by_hash(
2384 &mut self,
2385 _code_hash: B256,
2386 ) -> Result<revm::state::Bytecode, Self::Error> {
2387 Ok(revm::state::Bytecode::default())
2388 }
2389 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2390 if index == self.gas_backlog_slot {
2391 Ok(self.pre_existing_backlog)
2392 } else if index == self.speed_limit_slot {
2393 Ok(self.speed_limit_value)
2394 } else {
2395 Ok(U256::ZERO)
2396 }
2397 }
2398 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2399 Ok(B256::ZERO)
2400 }
2401 }
2402
2403 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0); let pre_existing_backlog = U256::from(552756u64);
2406
2407 let mut state = StateBuilder::new()
2408 .with_database(PrePopulatedDb2 {
2409 gas_backlog_slot,
2410 pre_existing_backlog,
2411 speed_limit_slot,
2412 speed_limit_value: U256::from(7_000_000u64), })
2414 .with_bundle_update()
2415 .build();
2416
2417 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2418 let state_ptr: *mut revm::database::State<PrePopulatedDb2> = &mut state;
2419
2420 let backing = Storage::new(state_ptr, B256::ZERO);
2421 let l2_pricing =
2422 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2423
2424 let _initial = l2_pricing.gas_backlog().unwrap();
2425
2426 l2_pricing.update_pricing_model(1, 10).unwrap();
2429 let _after_drain = l2_pricing.gas_backlog().unwrap();
2430
2431 {
2433 use revm::DatabaseCommit;
2434 state.commit(Default::default());
2435 }
2436
2437 let l2_pricing2 =
2439 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2440 l2_pricing2
2441 .grow_backlog(357751, MultiGas::default())
2442 .unwrap();
2443 let _after_grow = l2_pricing2.gas_backlog().unwrap();
2444
2445 state.merge_transitions(BundleRetention::Reverts);
2447 let mut bundle = state.take_bundle();
2448
2449 let _bundle_pre = bundle
2450 .state
2451 .get(&ARBOS_STATE_ADDRESS)
2452 .and_then(|a| a.storage.get(&gas_backlog_slot))
2453 .map(|s| (s.present_value, s.previous_or_original_value));
2454
2455 for (_addr, account) in bundle.state.iter_mut() {
2461 account
2462 .storage
2463 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2464 }
2465
2466 let final_slot = bundle
2467 .state
2468 .get(&ARBOS_STATE_ADDRESS)
2469 .and_then(|a| a.storage.get(&gas_backlog_slot))
2470 .map(|s| (s.present_value, s.previous_or_original_value));
2471
2472 assert!(
2476 final_slot.is_some(),
2477 "VARIANT F FAILED: gas_backlog slot MISSING from bundle (drain+grow)"
2478 );
2479 assert_eq!(
2480 final_slot.unwrap().0,
2481 U256::from(357751u64),
2482 "VARIANT F: gas_backlog should be 357751"
2483 );
2484 }
2485
2486 {
2495 struct PrePopDb3 {
2496 gas_backlog_slot: U256,
2497 speed_limit_slot: U256,
2498 }
2499
2500 impl Database for PrePopDb3 {
2501 type Error = std::convert::Infallible;
2502 fn basic(
2503 &mut self,
2504 _address: Address,
2505 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2506 Ok(Some(revm::state::AccountInfo {
2507 balance: U256::ZERO,
2508 nonce: 1,
2509 code_hash: keccak256([]),
2510 code: None,
2511 account_id: None,
2512 }))
2513 }
2514 fn code_by_hash(
2515 &mut self,
2516 _code_hash: B256,
2517 ) -> Result<revm::state::Bytecode, Self::Error> {
2518 Ok(revm::state::Bytecode::default())
2519 }
2520 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2521 if index == self.gas_backlog_slot {
2522 Ok(U256::from(552756u64))
2523 } else if index == self.speed_limit_slot {
2524 Ok(U256::from(7_000_000u64))
2525 } else {
2526 Ok(U256::ZERO)
2527 }
2528 }
2529 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2530 Ok(B256::ZERO)
2531 }
2532 }
2533
2534 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
2535
2536 let mut state = StateBuilder::new()
2537 .with_database(PrePopDb3 {
2538 gas_backlog_slot,
2539 speed_limit_slot,
2540 })
2541 .with_bundle_update()
2542 .build();
2543
2544 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2545 let state_ptr: *mut revm::database::State<PrePopDb3> = &mut state;
2546
2547 let backing = Storage::new(state_ptr, B256::ZERO);
2548 let l2_pricing =
2549 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2550
2551 let _initial = l2_pricing.gas_backlog().unwrap();
2552
2553 l2_pricing.update_pricing_model(1, 10).unwrap();
2555 let after_drain = l2_pricing.gas_backlog().unwrap();
2556 assert_eq!(after_drain, 0);
2557
2558 state.merge_transitions(BundleRetention::Reverts);
2560 let mut bundle = state.take_bundle();
2561
2562 let _pre_filter = bundle
2563 .state
2564 .get(&ARBOS_STATE_ADDRESS)
2565 .and_then(|a| a.storage.get(&gas_backlog_slot))
2566 .map(|s| (s.present_value, s.previous_or_original_value));
2567
2568 for (_addr, account) in bundle.state.iter_mut() {
2570 account
2571 .storage
2572 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2573 }
2574
2575 let final_slot = bundle
2576 .state
2577 .get(&ARBOS_STATE_ADDRESS)
2578 .and_then(|a| a.storage.get(&gas_backlog_slot))
2579 .map(|s| (s.present_value, s.previous_or_original_value));
2580
2581 assert!(
2583 final_slot.is_some(),
2584 "VARIANT G FAILED: drain-to-0 write was lost!"
2585 );
2586 }
2587 }
2588}