1use alloy_primitives::U256;
2use arb_chainspec::arbos_version as version;
3use arb_primitives::multigas::{MultiGas, NUM_RESOURCE_KIND, ResourceKind};
4use arb_storage::{StorageBackend, SystemStateBackend};
5use revm::Database;
6
7use super::{L2PricingError, 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<B: SystemStateBackend>(
32 &self,
33 backend: &mut B,
34 ) -> Result<GasModel, L2PricingError> {
35 if self.arbos_version >= version::ARBOS_VERSION_60 {
36 let mgc_len = self.multi_gas_constraints_length(backend)?;
37 if mgc_len > 0 {
38 return Ok(GasModel::MultiGasConstraints);
39 }
40 }
41 if self.arbos_version >= version::ARBOS_VERSION_50 {
42 let gc_len = self.gas_constraints_length(backend)?;
43 if gc_len > 0 {
44 return Ok(GasModel::SingleGasConstraints);
45 }
46 }
47 Ok(GasModel::Legacy)
48 }
49
50 pub fn grow_backlog<B: StorageBackend>(
52 &self,
53 backend: &mut B,
54 used_gas: u64,
55 used_multi_gas: MultiGas,
56 ) -> Result<(), L2PricingError> {
57 self.update_backlog(backend, BacklogOperation::Grow, used_gas, used_multi_gas)
58 }
59
60 pub fn shrink_backlog<B: StorageBackend>(
62 &self,
63 backend: &mut B,
64 used_gas: u64,
65 used_multi_gas: MultiGas,
66 ) -> Result<(), L2PricingError> {
67 self.update_backlog(backend, BacklogOperation::Shrink, used_gas, used_multi_gas)
68 }
69
70 fn update_backlog<B: StorageBackend>(
72 &self,
73 backend: &mut B,
74 op: BacklogOperation,
75 used_gas: u64,
76 used_multi_gas: MultiGas,
77 ) -> Result<(), L2PricingError> {
78 match self.gas_model_to_use(backend)? {
79 GasModel::Legacy | GasModel::Unknown => {
80 self.update_legacy_backlog_op(backend, op, used_gas)
81 }
82 GasModel::SingleGasConstraints => {
83 self.update_single_gas_constraints_backlogs_op(backend, op, used_gas)
84 }
85 GasModel::MultiGasConstraints => {
86 self.update_multi_gas_constraints_backlogs_op(backend, op, used_multi_gas)
87 }
88 }
89 }
90
91 fn update_legacy_backlog_op<B: StorageBackend>(
92 &self,
93 backend: &mut B,
94 op: BacklogOperation,
95 gas: u64,
96 ) -> Result<(), L2PricingError> {
97 let backlog = self.gas_backlog(backend)?;
98 let new_backlog = apply_gas_delta_op(op, backlog, gas);
99 self.set_gas_backlog(backend, new_backlog)
100 }
101
102 fn update_single_gas_constraints_backlogs_op<B: StorageBackend>(
103 &self,
104 backend: &mut B,
105 op: BacklogOperation,
106 gas: u64,
107 ) -> Result<(), L2PricingError> {
108 let len = self.gas_constraints_length(backend)?;
109 for i in 0..len {
110 let c = self.open_gas_constraint_at(i);
111 let backlog = c.backlog(backend)?;
112 c.set_backlog(backend, apply_gas_delta_op(op, backlog, gas))?;
113 }
114 Ok(())
115 }
116
117 fn update_multi_gas_constraints_backlogs_op<B: StorageBackend>(
118 &self,
119 backend: &mut B,
120 op: BacklogOperation,
121 multi_gas: MultiGas,
122 ) -> Result<(), L2PricingError> {
123 let len = self.multi_gas_constraints_length(backend)?;
124 for i in 0..len {
125 let c = self.open_multi_gas_constraint_at(i);
126 match op {
127 BacklogOperation::Grow => c.grow_backlog(backend, multi_gas)?,
128 BacklogOperation::Shrink => c.shrink_backlog(backend, multi_gas)?,
129 }
130 }
131 Ok(())
132 }
133
134 pub fn update_pricing_model<B: StorageBackend>(
136 &self,
137 backend: &mut B,
138 time_passed: u64,
139 arbos_version: u64,
140 ) -> Result<(), L2PricingError> {
141 let _ = arbos_version;
142 match self.gas_model_to_use(backend)? {
143 GasModel::Legacy | GasModel::Unknown => {
144 self.update_pricing_model_legacy(backend, time_passed)
145 }
146 GasModel::SingleGasConstraints => {
147 self.update_pricing_model_single_constraints(backend, time_passed)
148 }
149 GasModel::MultiGasConstraints => {
150 self.update_pricing_model_multi_constraints(backend, time_passed)
151 }
152 }
153 }
154
155 fn update_pricing_model_legacy<B: StorageBackend>(
156 &self,
157 backend: &mut B,
158 time_passed: u64,
159 ) -> Result<(), L2PricingError> {
160 let speed_limit = self.speed_limit_per_second(backend)?;
161 let drain = time_passed.saturating_mul(speed_limit);
162 self.update_legacy_backlog_op(backend, BacklogOperation::Shrink, drain)?;
163
164 let inertia = self.pricing_inertia(backend)?;
165 let tolerance = self.backlog_tolerance(backend)?;
166 let backlog = self.gas_backlog(backend)?;
167 let min_base_fee = self.min_base_fee_wei(backend)?;
168
169 let tolerance_limit = tolerance.wrapping_mul(speed_limit);
170 let base_fee = if backlog > tolerance_limit {
171 let divisor = saturating_cast_to_i64(inertia.saturating_mul(speed_limit));
172 if divisor == 0 {
173 return self.set_base_fee_wei(backend, min_base_fee);
174 }
175 let excess = saturating_cast_to_i64(backlog.wrapping_sub(tolerance_limit));
176 let exponent_bips = natural_to_bips(excess) / divisor;
177 self.calc_base_fee_from_exponent(backend, exponent_bips.max(0) as u64)?
178 } else {
179 min_base_fee
180 };
181
182 self.set_base_fee_wei(backend, base_fee)
183 }
184
185 fn update_pricing_model_single_constraints<B: StorageBackend>(
186 &self,
187 backend: &mut B,
188 time_passed: u64,
189 ) -> Result<(), L2PricingError> {
190 let mut total_exponent: i64 = 0;
191 let len = self.gas_constraints_length(backend)?;
192
193 for i in 0..len {
194 let c = self.open_gas_constraint_at(i);
195 let target = c.target(backend)?;
196
197 let backlog = c.backlog(backend)?;
198 let gas = time_passed.saturating_mul(target);
199 let new_backlog = backlog.saturating_sub(gas);
200 c.set_backlog(backend, new_backlog)?;
201
202 if new_backlog > 0 {
203 let window = c.adjustment_window(backend)?;
204 let divisor = saturating_cast_to_i64(window.saturating_mul(target));
205 if divisor != 0 {
206 let exponent = natural_to_bips(saturating_cast_to_i64(new_backlog)) / divisor;
207 total_exponent = total_exponent.saturating_add(exponent);
208 }
209 }
210 }
211
212 let base_fee = self.calc_base_fee_from_exponent(backend, total_exponent.max(0) as u64)?;
213 self.set_base_fee_wei(backend, base_fee)
214 }
215
216 fn update_pricing_model_multi_constraints<B: StorageBackend>(
217 &self,
218 backend: &mut B,
219 time_passed: u64,
220 ) -> Result<(), L2PricingError> {
221 self.update_multi_gas_constraints_backlogs(backend, time_passed)?;
222
223 let exponent_per_kind = self.calc_multi_gas_constraints_exponents(backend)?;
224
225 let mut max_base_fee = self.min_base_fee_wei(backend)?;
226 let fees = &self.multi_gas_base_fees;
227
228 for (i, &exp) in exponent_per_kind.iter().enumerate() {
229 let base_fee = self.calc_base_fee_from_exponent(backend, exp)?;
230 if let Some(kind) = ResourceKind::from_u8(i as u8) {
231 let mgf = super::multi_gas_fees::open_multi_gas_fees(fees.clone());
232 mgf.set_next_block_fee(backend, kind, base_fee)?;
233 }
234 if base_fee > max_base_fee {
235 max_base_fee = base_fee;
236 }
237 }
238
239 self.set_base_fee_wei(backend, max_base_fee)
240 }
241
242 fn update_multi_gas_constraints_backlogs<B: StorageBackend>(
243 &self,
244 backend: &mut B,
245 time_passed: u64,
246 ) -> Result<(), L2PricingError> {
247 let len = self.multi_gas_constraints_length(backend)?;
248 for i in 0..len {
249 let c = self.open_multi_gas_constraint_at(i);
250 let target = c.target(backend)?;
251 let backlog = c.backlog(backend)?;
252 let gas = time_passed.saturating_mul(target);
253 let new_backlog = backlog.saturating_sub(gas);
254 c.set_backlog(backend, new_backlog)?;
255 }
256 Ok(())
257 }
258
259 pub fn calc_multi_gas_constraints_exponents<B: StorageBackend>(
261 &self,
262 backend: &mut B,
263 ) -> Result<[u64; NUM_RESOURCE_KIND], L2PricingError> {
264 let len = self.multi_gas_constraints_length(backend)?;
265 let mut exponent_per_kind = [0i64; NUM_RESOURCE_KIND];
266
267 for i in 0..len {
268 let c = self.open_multi_gas_constraint_at(i);
269 let target = c.target(backend)?;
270 let backlog = c.backlog(backend)?;
271
272 if backlog == 0 {
273 continue;
274 }
275
276 let window = c.adjustment_window(backend)?;
277 let max_weight = c.max_weight(backend)?;
278
279 if target == 0 || window == 0 || max_weight == 0 {
280 continue;
281 }
282
283 let divisor_u64 = (window as u64).saturating_mul(target.saturating_mul(max_weight));
284 let divisor = saturating_cast_to_i64(divisor_u64);
285 if divisor == 0 {
286 continue;
287 }
288
289 for kind in ResourceKind::ALL {
290 if kind == ResourceKind::SingleDim {
291 continue;
292 }
293 let weight = c.resource_weight(backend, kind)?;
294 if weight == 0 {
295 continue;
296 }
297
298 let product = backlog.saturating_mul(weight);
299 let cast = saturating_cast_to_i64(product);
300 let dividend = natural_to_bips(cast);
301
302 let exp = dividend / divisor;
303 exponent_per_kind[kind as usize] =
304 exponent_per_kind[kind as usize].saturating_add(exp);
305 }
306 }
307
308 let mut result = [0u64; NUM_RESOURCE_KIND];
309 for i in 0..NUM_RESOURCE_KIND {
310 result[i] = exponent_per_kind[i].max(0) as u64;
311 }
312 Ok(result)
313 }
314
315 pub fn calc_base_fee_from_exponent<B: StorageBackend>(
318 &self,
319 backend: &mut B,
320 exponent_bips: u64,
321 ) -> Result<U256, L2PricingError> {
322 let min_base_fee = self.min_base_fee_wei(backend)?;
323 if exponent_bips == 0 {
324 return Ok(min_base_fee);
325 }
326
327 let exp_result = arb_math::approx_exp_basis_points(exponent_bips, 4);
328 let base_fee = (min_base_fee * U256::from(exp_result)) / U256::from(10000u64);
329
330 if base_fee < min_base_fee {
331 Ok(min_base_fee)
332 } else {
333 Ok(base_fee)
334 }
335 }
336
337 pub fn get_multi_gas_base_fee_per_resource<B: SystemStateBackend>(
338 &self,
339 backend: &mut B,
340 ) -> Result<[U256; NUM_RESOURCE_KIND], L2PricingError> {
341 let base_fee = self.base_fee_wei(backend)?;
342 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
343 let mut fees = [U256::ZERO; NUM_RESOURCE_KIND];
344 for kind in ResourceKind::ALL {
345 if kind == ResourceKind::SingleDim {
346 fees[kind as usize] = base_fee;
347 continue;
348 }
349 let fee = mgf.get_current_block_fee(backend, kind)?;
350 fees[kind as usize] = if fee.is_zero() { base_fee } else { fee };
351 }
352 Ok(fees)
353 }
354
355 pub fn get_current_multi_gas_fees<B: SystemStateBackend>(
361 &self,
362 backend: &mut B,
363 ) -> Result<[U256; NUM_RESOURCE_KIND], L2PricingError> {
364 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
365 let mut fees = [U256::ZERO; NUM_RESOURCE_KIND];
366 for kind in ResourceKind::ALL {
367 if kind == ResourceKind::SingleDim {
368 continue;
369 }
370 fees[kind as usize] = mgf.get_current_block_fee(backend, kind)?;
371 }
372 Ok(fees)
373 }
374
375 pub fn commit_multi_gas_fees<B: StorageBackend>(
377 &self,
378 backend: &mut B,
379 ) -> Result<(), L2PricingError> {
380 if self.gas_model_to_use(backend)? != GasModel::MultiGasConstraints {
381 return Ok(());
382 }
383 let mgf = super::multi_gas_fees::open_multi_gas_fees(self.multi_gas_base_fees.clone());
384 mgf.commit_next_to_current(backend)
385 }
386
387 pub fn backlog_update_cost<B: SystemStateBackend>(
389 &self,
390 backend: &mut B,
391 ) -> Result<u64, L2PricingError> {
392 use super::{STORAGE_READ_COST, STORAGE_WRITE_COST};
393
394 if self.arbos_version >= version::ARBOS_VERSION_60 {
395 return Ok(MULTI_CONSTRAINT_STATIC_BACKLOG_UPDATE_COST);
396 }
397
398 let mut result = 0u64;
399
400 if self.arbos_version >= version::ARBOS_VERSION_50 {
401 result += STORAGE_READ_COST;
402 }
403
404 if self.arbos_version >= version::ARBOS_VERSION_MULTI_CONSTRAINT_FIX {
405 let constraints_length = self.gas_constraints_length(backend)?;
406 if constraints_length > 0 {
407 result += STORAGE_READ_COST;
408 result += constraints_length * (STORAGE_READ_COST + STORAGE_WRITE_COST);
409 return Ok(result);
410 }
411 }
412
413 result += STORAGE_READ_COST + STORAGE_WRITE_COST;
414
415 Ok(result)
416 }
417
418 pub fn set_gas_constraints_from_legacy<B: StorageBackend>(
420 &self,
421 backend: &mut B,
422 ) -> Result<(), L2PricingError> {
423 self.clear_gas_constraints(backend)?;
424 let target = self.speed_limit_per_second(backend)?;
425 let adjustment_window = self.pricing_inertia(backend)?;
426 let old_backlog = self.gas_backlog(backend)?;
427 let backlog_tolerance = self.backlog_tolerance(backend)?;
428
429 let backlog = old_backlog.saturating_sub(backlog_tolerance.saturating_mul(target));
430 self.add_gas_constraint(backend, target, adjustment_window, backlog)
431 }
432
433 pub fn set_multi_gas_constraints_from_single_gas_constraints<B: StorageBackend>(
435 &self,
436 backend: &mut B,
437 ) -> Result<(), L2PricingError> {
438 self.clear_multi_gas_constraints(backend)?;
439
440 let length = self.gas_constraints_length(backend)?;
441
442 for i in 0..length {
443 let c = self.open_gas_constraint_at(i);
444
445 let target = c.target(backend)?;
446 let window = c.adjustment_window(backend)?;
447 let backlog = c.backlog(backend)?;
448
449 let weights = [1u64; NUM_RESOURCE_KIND];
450
451 let adjustment_window: u32 = if window > u32::MAX as u64 {
452 u32::MAX
453 } else {
454 window as u32
455 };
456
457 self.add_multi_gas_constraint(backend, target, adjustment_window, backlog, &weights)?;
458 }
459 Ok(())
460 }
461
462 pub fn multi_dimensional_price_for_refund<B: SystemStateBackend>(
466 &self,
467 backend: &mut B,
468 gas_used: MultiGas,
469 ) -> Result<U256, L2PricingError> {
470 let fees = self.get_multi_gas_base_fee_per_resource(backend)?;
471 let mut total = U256::ZERO;
472 for kind in ResourceKind::ALL {
473 let amount = gas_used.get(kind);
474 if amount == 0 {
475 continue;
476 }
477 total = total.saturating_add(U256::from(amount).saturating_mul(fees[kind as usize]));
478 }
479 Ok(total)
480 }
481
482 pub fn multi_dimensional_price_for_refund_with_fees<B: SystemStateBackend>(
491 &self,
492 backend: &mut B,
493 gas_used: MultiGas,
494 cached_fees: &[U256; NUM_RESOURCE_KIND],
495 ) -> Result<U256, L2PricingError> {
496 let base_fee = self.base_fee_wei(backend)?;
497 let mut total = U256::ZERO;
498 for kind in ResourceKind::ALL {
499 let amount = gas_used.get(kind);
500 if amount == 0 {
501 continue;
502 }
503 let fee = if kind == ResourceKind::SingleDim {
504 base_fee
505 } else {
506 let cached = cached_fees[kind as usize];
507 if cached.is_zero() { base_fee } else { cached }
508 };
509 total = total.saturating_add(U256::from(amount).saturating_mul(fee));
510 }
511 Ok(total)
512 }
513}
514
515fn saturating_cast_to_i64(value: u64) -> i64 {
517 if value > i64::MAX as u64 {
518 i64::MAX
519 } else {
520 value as i64
521 }
522}
523
524fn natural_to_bips(natural: i64) -> i64 {
526 natural.saturating_mul(10000)
527}
528
529pub fn apply_gas_delta(backlog: u64, delta: i64) -> u64 {
531 if delta > 0 {
532 backlog.saturating_add(delta as u64)
533 } else {
534 backlog.saturating_sub((-delta) as u64)
535 }
536}
537
538fn apply_gas_delta_op(op: BacklogOperation, backlog: u64, delta: u64) -> u64 {
540 match op {
541 BacklogOperation::Grow => backlog.saturating_add(delta),
542 BacklogOperation::Shrink => backlog.saturating_sub(delta),
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use alloy_primitives::{Address, B256, U256, address, keccak256};
549 use arb_primitives::multigas::MultiGas;
550 use arb_storage::Storage;
551 use revm::{Database, database::StateBuilder};
552
553 const ARBOS_STATE_ADDRESS: Address = address!("A4B05FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
554
555 #[derive(Default)]
556 struct EmptyDb;
557
558 impl Database for EmptyDb {
559 type Error = std::convert::Infallible;
560 fn basic(
561 &mut self,
562 _address: Address,
563 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
564 Ok(None)
565 }
566 fn code_by_hash(&mut self, _code_hash: B256) -> Result<revm::state::Bytecode, Self::Error> {
567 Ok(revm::state::Bytecode::default())
568 }
569 fn storage(&mut self, _address: Address, _index: U256) -> Result<U256, Self::Error> {
570 Ok(U256::ZERO)
571 }
572 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
573 Ok(B256::ZERO)
574 }
575 }
576
577 fn ensure_cache_account(state: &mut revm::database::State<EmptyDb>, addr: Address) {
579 use revm::database::{PlainAccount, states::account_status::AccountStatus};
580
581 let _ = state.load_cache_account(addr);
582 if let Some(cached) = state.cache.accounts.get_mut(&addr)
583 && cached.account.is_none()
584 {
585 cached.account = Some(PlainAccount {
586 info: revm::state::AccountInfo {
587 balance: U256::ZERO,
588 nonce: 0,
589 code_hash: keccak256([]),
590 code: None,
591 account_id: None,
592 },
593 storage: Default::default(),
594 });
595 cached.status = AccountStatus::InMemoryChange;
596 }
597 }
598
599 #[test]
600 fn test_grow_backlog_through_l2_pricing_state() {
601 let mut state = StateBuilder::new()
602 .with_database(EmptyDb)
603 .with_bundle_update()
604 .build();
605
606 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
608 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
609
610 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
611
612 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
614 let l2_sto = backing.open_sub_storage(&[1]);
615
616 super::super::initialize_l2_pricing_state(&l2_sto, unsafe { &mut *state_ptr }).unwrap();
618
619 let l2_pricing = super::super::open_l2_pricing_state(
621 backing.open_sub_storage(&[1]),
622 10, );
624 let initial_backlog = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
625 assert_eq!(initial_backlog, 0, "Initial gasBacklog should be 0");
626
627 let result =
629 l2_pricing.grow_backlog(unsafe { &mut *state_ptr }, 100_000, MultiGas::default());
630 assert!(result.is_ok(), "grow_backlog should succeed");
631
632 let after_grow = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
634 assert_eq!(
635 after_grow, 100_000,
636 "gasBacklog should be 100000 after grow"
637 );
638
639 let result =
641 l2_pricing.grow_backlog(unsafe { &mut *state_ptr }, 50_000, MultiGas::default());
642 assert!(result.is_ok(), "second grow_backlog should succeed");
643
644 let after_second_grow = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
645 assert_eq!(
646 after_second_grow, 150_000,
647 "gasBacklog should be 150000 after second grow"
648 );
649
650 let result =
652 l2_pricing.shrink_backlog(unsafe { &mut *state_ptr }, 30_000, MultiGas::default());
653 assert!(result.is_ok(), "shrink_backlog should succeed");
654
655 let after_shrink = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
656 assert_eq!(
657 after_shrink, 120_000,
658 "gasBacklog should be 120000 after shrink"
659 );
660
661 use revm::database::states::bundle_state::BundleRetention;
663 state.merge_transitions(BundleRetention::Reverts);
664 let bundle = state.take_bundle();
665
666 let acct = bundle
667 .state
668 .get(&ARBOS_STATE_ADDRESS)
669 .expect("ArbOS account should be in bundle");
670
671 let l2_base = keccak256([1u8]); let gas_backlog_offset: u64 = 4;
675 let slot = arb_storage::storage_key_map(l2_base.as_slice(), gas_backlog_offset);
676
677 let bundle_slot = acct
678 .storage
679 .get(&slot)
680 .expect("gasBacklog slot should be in bundle");
681 assert_eq!(
682 bundle_slot.present_value,
683 U256::from(120_000u64),
684 "Bundle should contain final gasBacklog value"
685 );
686 }
687
688 #[test]
693 fn grow_backlog_survives_submit_retryable_then_retry_tx_flow() {
694 use alloy_primitives::map::HashMap;
695 use revm::{DatabaseCommit, database::states::bundle_state::BundleRetention};
696
697 let l2_base = keccak256([1u8]); let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
700 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
701 let per_block_gas_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 1);
702 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
703 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
704 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
705 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
706
707 let version_slot = arb_storage::storage_key_map(&[], 0);
709
710 struct PreloadedDb {
712 slots: HashMap<(Address, U256), U256>,
713 }
714
715 impl PreloadedDb {
716 fn new() -> Self {
717 Self {
718 slots: HashMap::default(),
719 }
720 }
721 fn set(&mut self, addr: Address, slot: U256, val: U256) {
722 self.slots.insert((addr, slot), val);
723 }
724 }
725
726 impl Database for PreloadedDb {
727 type Error = std::convert::Infallible;
728 fn basic(
729 &mut self,
730 addr: Address,
731 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
732 if addr == ARBOS_STATE_ADDRESS {
733 Ok(Some(revm::state::AccountInfo {
734 nonce: 1,
735 balance: U256::ZERO,
736 code_hash: keccak256([]),
737 code: None,
738 account_id: None,
739 }))
740 } else {
741 Ok(None)
742 }
743 }
744 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
745 Ok(revm::state::Bytecode::default())
746 }
747 fn storage(&mut self, addr: Address, index: U256) -> Result<U256, Self::Error> {
748 Ok(self
749 .slots
750 .get(&(addr, index))
751 .copied()
752 .unwrap_or(U256::ZERO))
753 }
754 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
755 Ok(B256::ZERO)
756 }
757 }
758
759 let arbos = ARBOS_STATE_ADDRESS;
760
761 let mut db = PreloadedDb::new();
763 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
764 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
765 db.set(arbos, per_block_gas_limit_slot, U256::from(32_000_000u64));
766 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
767 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
768 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
769 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
770 db.set(arbos, version_slot, U256::from(20u64)); let mut state = StateBuilder::new()
773 .with_database(db)
774 .with_bundle_update()
775 .build();
776
777 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
778
779 {
784 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
785 let l2_sto = backing.open_sub_storage(&[1]);
786 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
787
788 let result = l2_pricing.update_pricing_model(unsafe { &mut *state_ptr }, 0, 20);
790 assert!(result.is_ok(), "update_pricing_model should succeed");
791
792 let backlog = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
794 assert_eq!(
795 backlog, 552_756,
796 "gasBacklog should be 552756 after no-op drain"
797 );
798 }
799
800 let empty_changes: HashMap<Address, revm::state::Account> = Default::default();
802 state.commit(empty_changes);
803
804 {
809 let retryable_base = keccak256([2u8]); for i in 0u64..10 {
812 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
813 let _ = arb_storage::write_storage_at(
814 unsafe { &mut *state_ptr },
815 arbos,
816 slot,
817 U256::from(1000 + i),
818 );
819 }
820
821 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5); let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
824 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
825 let _ = arb_storage::write_storage_at(
826 unsafe { &mut *state_ptr },
827 arbos,
828 scratch_slot_1,
829 U256::from(42),
830 );
831 let _ = arb_storage::write_storage_at(
832 unsafe { &mut *state_ptr },
833 arbos,
834 scratch_slot_2,
835 U256::from(43),
836 );
837 let _ = arb_storage::write_storage_at(
838 unsafe { &mut *state_ptr },
839 arbos,
840 scratch_slot_3,
841 U256::from(44),
842 );
843 }
844
845 let empty_changes2: HashMap<Address, revm::state::Account> = Default::default();
847 state.commit(empty_changes2);
848
849 {
851 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
852 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
853 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
854 let _ = arb_storage::write_arbos_storage(
855 unsafe { &mut *state_ptr },
856 scratch_slot_1,
857 U256::ZERO,
858 );
859 let _ = arb_storage::write_arbos_storage(
860 unsafe { &mut *state_ptr },
861 scratch_slot_2,
862 U256::ZERO,
863 );
864 let _ = arb_storage::write_arbos_storage(
865 unsafe { &mut *state_ptr },
866 scratch_slot_3,
867 U256::ZERO,
868 );
869 }
870
871 {
877 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
878 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
879 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
880 let _ = arb_storage::write_storage_at(
881 unsafe { &mut *state_ptr },
882 arbos,
883 scratch_slot_1,
884 U256::from(99),
885 );
886 let _ = arb_storage::write_storage_at(
887 unsafe { &mut *state_ptr },
888 arbos,
889 scratch_slot_2,
890 U256::from(100),
891 );
892 let _ = arb_storage::write_storage_at(
893 unsafe { &mut *state_ptr },
894 arbos,
895 scratch_slot_3,
896 U256::from(101),
897 );
898 }
899
900 {
904 let mut evm_changes: HashMap<Address, revm::state::Account> = Default::default();
905
906 let sender = address!("fd86e9a33fd52e4085fb94d24b759448a621cd36");
908 let _ = state.load_cache_account(sender);
909 let mut sender_acct = revm::state::Account::default();
910 sender_acct.info.balance = U256::from(1_000_000_000u64);
911 sender_acct.info.nonce = 1;
912 sender_acct.mark_touch();
913 evm_changes.insert(sender, sender_acct);
914
915 let contracts = [
917 address!("4453d0eaf066a61c9b81ddc18bb5a2bf2fc52224"),
918 address!("7c7db13e5d385bcc797422d3c767856d15d24c5c"),
919 address!("0057892cb8bb5f1ce1b3c6f5ade899732249713f"),
920 address!("35aa95ac4747d928e2cd42fe4461f6d9d1826346"),
921 address!("e1e3b1cbacc870cb6e5f4bdf246feb6eb5cd351b"),
922 address!("7348fdf6f3e090c635b23d970945093455214f3b"),
923 address!("d50e4a971bc8ed55af6aebc0a2178456069e87b5"),
924 ];
925
926 for (i, &contract) in contracts.iter().enumerate() {
927 let _ = state.load_cache_account(contract);
928 let mut acct = revm::state::Account::default();
929 acct.info.nonce = 1;
930 acct.info.code_hash = keccak256(format!("code_{}", i).as_bytes());
931 acct.mark_touch();
932 for j in 0u64..3 {
934 let slot = U256::from(j);
935 let mut evm_slot =
936 revm::state::EvmStorageSlot::new(U256::from(i as u64 * 100 + j), 0);
937 evm_slot.present_value = U256::from(i as u64 * 100 + j + 1);
938 acct.storage.insert(slot, evm_slot);
939 }
940 evm_changes.insert(contract, acct);
941 }
942
943 state.commit(evm_changes);
944 }
945
946 {
948 let scratch_slot_1 = arb_storage::storage_key_map(&[], 5);
949 let scratch_slot_2 = arb_storage::storage_key_map(&[], 6);
950 let scratch_slot_3 = arb_storage::storage_key_map(&[], 7);
951 let _ = arb_storage::write_arbos_storage(
952 unsafe { &mut *state_ptr },
953 scratch_slot_1,
954 U256::ZERO,
955 );
956 let _ = arb_storage::write_arbos_storage(
957 unsafe { &mut *state_ptr },
958 scratch_slot_2,
959 U256::ZERO,
960 );
961 let _ = arb_storage::write_arbos_storage(
962 unsafe { &mut *state_ptr },
963 scratch_slot_3,
964 U256::ZERO,
965 );
966 }
967
968 {
970 let retryable_base = keccak256([2u8]);
971 for i in 0u64..10 {
972 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
973 let _ = arb_storage::write_storage_at(
974 unsafe { &mut *state_ptr },
975 arbos,
976 slot,
977 U256::ZERO,
978 );
979 }
980 }
981
982 {
984 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
985 let l2_sto = backing.open_sub_storage(&[1]);
986 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
987
988 let backlog_before = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
989 assert_eq!(
990 backlog_before, 552_756,
991 "gasBacklog should still be 552756 before grow"
992 );
993
994 let result =
995 l2_pricing.grow_backlog(unsafe { &mut *state_ptr }, 357_751, MultiGas::default());
996 assert!(result.is_ok(), "grow_backlog should succeed");
997
998 let backlog_after = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
999 assert_eq!(
1000 backlog_after, 910_507,
1001 "gasBacklog should be 910507 after grow"
1002 );
1003 }
1004
1005 state.merge_transitions(BundleRetention::Reverts);
1009 let mut bundle = state.take_bundle();
1010
1011 let pre_filter_backlog = bundle
1013 .state
1014 .get(&arbos)
1015 .and_then(|a| a.storage.get(&gas_backlog_slot))
1016 .map(|s| s.present_value);
1017 assert_eq!(
1018 pre_filter_backlog,
1019 Some(U256::from(910_507u64)),
1020 "gasBacklog should be in bundle before filter with value 910507"
1021 );
1022
1023 for (_addr, account) in bundle.state.iter_mut() {
1025 account
1026 .storage
1027 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1028 }
1029
1030 let post_filter_backlog = bundle
1032 .state
1033 .get(&arbos)
1034 .and_then(|a| a.storage.get(&gas_backlog_slot))
1035 .map(|s| s.present_value);
1036 assert_eq!(
1037 post_filter_backlog,
1038 Some(U256::from(910_507u64)),
1039 "gasBacklog should survive filter_unchanged_storage with value 910507"
1040 );
1041
1042 let original = bundle
1044 .state
1045 .get(&arbos)
1046 .and_then(|a| a.storage.get(&gas_backlog_slot))
1047 .map(|s| s.previous_or_original_value);
1048 assert_eq!(
1049 original,
1050 Some(U256::from(552_756u64)),
1051 "gasBacklog original should be the pre-block DB value 552756"
1052 );
1053
1054 let cache_backlog = state
1058 .cache
1059 .accounts
1060 .get(&arbos)
1061 .and_then(|ca| ca.account.as_ref())
1062 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1063 assert_eq!(
1064 cache_backlog,
1065 Some(U256::from(910_507u64)),
1066 "gasBacklog should be in cache with value 910507"
1067 );
1068 }
1069
1070 #[test]
1073 fn grow_backlog_with_arbos_in_evm_commit() {
1074 use alloy_primitives::map::HashMap;
1075 use revm::{DatabaseCommit, database::states::bundle_state::BundleRetention};
1076
1077 let l2_base = keccak256([1u8]);
1078 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
1079 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
1080 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
1081 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
1082 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
1083 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
1084 let version_slot = arb_storage::storage_key_map(&[], 0);
1085 let scratch_1 = arb_storage::storage_key_map(&[], 5);
1087 let scratch_2 = arb_storage::storage_key_map(&[], 6);
1088
1089 struct PreloadedDb {
1090 slots: HashMap<(Address, U256), U256>,
1091 }
1092 impl PreloadedDb {
1093 fn new() -> Self {
1094 Self {
1095 slots: HashMap::default(),
1096 }
1097 }
1098 fn set(&mut self, addr: Address, slot: U256, val: U256) {
1099 self.slots.insert((addr, slot), val);
1100 }
1101 }
1102 impl Database for PreloadedDb {
1103 type Error = std::convert::Infallible;
1104 fn basic(
1105 &mut self,
1106 addr: Address,
1107 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
1108 if addr == ARBOS_STATE_ADDRESS {
1109 Ok(Some(revm::state::AccountInfo {
1110 nonce: 1,
1111 balance: U256::ZERO,
1112 code_hash: keccak256([]),
1113 code: None,
1114 account_id: None,
1115 }))
1116 } else {
1117 Ok(None)
1118 }
1119 }
1120 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
1121 Ok(revm::state::Bytecode::default())
1122 }
1123 fn storage(&mut self, addr: Address, index: U256) -> Result<U256, Self::Error> {
1124 Ok(self
1125 .slots
1126 .get(&(addr, index))
1127 .copied()
1128 .unwrap_or(U256::ZERO))
1129 }
1130 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
1131 Ok(B256::ZERO)
1132 }
1133 }
1134
1135 let arbos = ARBOS_STATE_ADDRESS;
1136 let mut db = PreloadedDb::new();
1137 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
1138 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
1139 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
1140 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
1141 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
1142 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
1143 db.set(arbos, version_slot, U256::from(20u64));
1144
1145 let mut state = StateBuilder::new()
1146 .with_database(db)
1147 .with_bundle_update()
1148 .build();
1149 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
1150
1151 {
1153 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1154 let l2_sto = backing.open_sub_storage(&[1]);
1155 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1156 let _ = l2_pricing.update_pricing_model(unsafe { &mut *state_ptr }, 0, 20);
1157 }
1158 state.commit(HashMap::default());
1159
1160 {
1162 let _ = arb_storage::write_storage_at(
1163 unsafe { &mut *state_ptr },
1164 arbos,
1165 scratch_1,
1166 U256::from(42),
1167 );
1168 let _ = arb_storage::write_storage_at(
1169 unsafe { &mut *state_ptr },
1170 arbos,
1171 scratch_2,
1172 U256::from(43),
1173 );
1174 let retryable_base = keccak256([2u8]);
1175 for i in 0u64..5 {
1176 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
1177 let _ = arb_storage::write_storage_at(
1178 unsafe { &mut *state_ptr },
1179 arbos,
1180 slot,
1181 U256::from(1000 + i),
1182 );
1183 }
1184 }
1185 state.commit(HashMap::default());
1186 let _ = arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_1, U256::ZERO);
1188 let _ = arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_2, U256::ZERO);
1189
1190 let _ = arb_storage::write_storage_at(
1192 unsafe { &mut *state_ptr },
1193 arbos,
1194 scratch_1,
1195 U256::from(99),
1196 );
1197 let _ = arb_storage::write_storage_at(
1198 unsafe { &mut *state_ptr },
1199 arbos,
1200 scratch_2,
1201 U256::from(100),
1202 );
1203
1204 {
1206 let mut evm_changes: HashMap<Address, revm::state::Account> = Default::default();
1207
1208 let sender = address!("fd86e9a33fd52e4085fb94d24b759448a621cd36");
1210 let _ = state.load_cache_account(sender);
1211 let mut sender_acct = revm::state::Account::default();
1212 sender_acct.info.balance = U256::from(1_000_000_000u64);
1213 sender_acct.info.nonce = 1;
1214 sender_acct.mark_touch();
1215 evm_changes.insert(sender, sender_acct);
1216
1217 let _ = state.load_cache_account(arbos);
1220 let mut arbos_acct = revm::state::Account {
1221 info: revm::state::AccountInfo {
1222 nonce: 1,
1223 balance: U256::ZERO,
1224 code_hash: keccak256([]),
1225 code: None,
1226 account_id: None,
1227 },
1228 ..Default::default()
1229 };
1230 arbos_acct.storage.insert(
1233 scratch_1,
1234 revm::state::EvmStorageSlot::new(U256::from(99), 0),
1235 );
1236 arbos_acct.mark_touch();
1237 evm_changes.insert(arbos, arbos_acct);
1238
1239 state.commit(evm_changes);
1240 }
1241
1242 let _backlog_check =
1244 arb_storage::read_storage_at(unsafe { &mut *state_ptr }, arbos, gas_backlog_slot);
1245
1246 let _ = arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_1, U256::ZERO);
1248 let _ = arb_storage::write_arbos_storage(unsafe { &mut *state_ptr }, scratch_2, U256::ZERO);
1249
1250 {
1252 let retryable_base = keccak256([2u8]);
1253 for i in 0u64..5 {
1254 let slot = arb_storage::storage_key_map(retryable_base.as_slice(), i);
1255 let _ = arb_storage::write_storage_at(
1256 unsafe { &mut *state_ptr },
1257 arbos,
1258 slot,
1259 U256::ZERO,
1260 );
1261 }
1262 }
1263
1264 {
1266 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1267 let l2_sto = backing.open_sub_storage(&[1]);
1268 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1269
1270 let backlog_before = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1271 assert_eq!(
1272 backlog_before, 552_756,
1273 "gasBacklog should be 552756 before grow"
1274 );
1275
1276 let _ =
1277 l2_pricing.grow_backlog(unsafe { &mut *state_ptr }, 357_751, MultiGas::default());
1278
1279 let backlog_after = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1280 assert_eq!(backlog_after, 910_507, "gasBacklog should be 910507");
1281 }
1282
1283 state.merge_transitions(BundleRetention::Reverts);
1285 let mut bundle = state.take_bundle();
1286
1287 let pre_filter = bundle
1288 .state
1289 .get(&arbos)
1290 .and_then(|a| a.storage.get(&gas_backlog_slot))
1291 .map(|s| (s.present_value, s.previous_or_original_value));
1292 assert_eq!(
1293 pre_filter.map(|p| p.0),
1294 Some(U256::from(910_507u64)),
1295 "gasBacklog should be 910507 in bundle before filter"
1296 );
1297
1298 for (_addr, account) in bundle.state.iter_mut() {
1300 account
1301 .storage
1302 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1303 }
1304
1305 let post_filter = bundle
1306 .state
1307 .get(&arbos)
1308 .and_then(|a| a.storage.get(&gas_backlog_slot))
1309 .map(|s| s.present_value);
1310 assert_eq!(
1311 post_filter,
1312 Some(U256::from(910_507u64)),
1313 "gasBacklog should survive filter when ArbOS is in EVM commit"
1314 );
1315 }
1316
1317 #[test]
1321 fn grow_backlog_with_transition_state_consumed() {
1322 use alloy_primitives::map::HashMap;
1323 use revm::{
1324 DatabaseCommit,
1325 database::states::{bundle_state::BundleRetention, plain_account::StorageSlot},
1326 };
1327
1328 let l2_base = keccak256([1u8]);
1329 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), 4);
1330 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
1331 let base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 2);
1332 let min_base_fee_slot = arb_storage::storage_key_map(l2_base.as_slice(), 3);
1333 let pricing_inertia_slot = arb_storage::storage_key_map(l2_base.as_slice(), 5);
1334 let backlog_tolerance_slot = arb_storage::storage_key_map(l2_base.as_slice(), 6);
1335 let version_slot = arb_storage::storage_key_map(&[], 0);
1336
1337 struct PreloadedDb(HashMap<(Address, U256), U256>);
1338 impl PreloadedDb {
1339 fn new() -> Self {
1340 Self(HashMap::default())
1341 }
1342 fn set(&mut self, a: Address, s: U256, v: U256) {
1343 self.0.insert((a, s), v);
1344 }
1345 }
1346 impl Database for PreloadedDb {
1347 type Error = std::convert::Infallible;
1348 fn basic(
1349 &mut self,
1350 addr: Address,
1351 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
1352 if addr == ARBOS_STATE_ADDRESS {
1353 Ok(Some(revm::state::AccountInfo {
1354 nonce: 1,
1355 balance: U256::ZERO,
1356 code_hash: keccak256([]),
1357 code: None,
1358 account_id: None,
1359 }))
1360 } else {
1361 Ok(None)
1362 }
1363 }
1364 fn code_by_hash(&mut self, _: B256) -> Result<revm::state::Bytecode, Self::Error> {
1365 Ok(revm::state::Bytecode::default())
1366 }
1367 fn storage(&mut self, a: Address, i: U256) -> Result<U256, Self::Error> {
1368 Ok(self.0.get(&(a, i)).copied().unwrap_or(U256::ZERO))
1369 }
1370 fn block_hash(&mut self, _: u64) -> Result<B256, Self::Error> {
1371 Ok(B256::ZERO)
1372 }
1373 }
1374
1375 let arbos = ARBOS_STATE_ADDRESS;
1376 let mut db = PreloadedDb::new();
1377 db.set(arbos, gas_backlog_slot, U256::from(552_756u64));
1378 db.set(arbos, speed_limit_slot, U256::from(7_000_000u64));
1379 db.set(arbos, base_fee_slot, U256::from(100_000_000u64));
1380 db.set(arbos, min_base_fee_slot, U256::from(100_000_000u64));
1381 db.set(arbos, pricing_inertia_slot, U256::from(102u64));
1382 db.set(arbos, backlog_tolerance_slot, U256::from(10u64));
1383 db.set(arbos, version_slot, U256::from(20u64));
1384
1385 let mut state = StateBuilder::new()
1386 .with_database(db)
1387 .with_bundle_update()
1388 .build();
1389 let state_ptr: *mut revm::database::State<PreloadedDb> = &mut state;
1390
1391 {
1393 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1394 let l2_sto = backing.open_sub_storage(&[1]);
1395 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1396 let _ = l2_pricing.update_pricing_model(unsafe { &mut *state_ptr }, 0, 20);
1397 }
1398 state.commit(HashMap::default());
1399
1400 state.merge_transitions(BundleRetention::Reverts);
1405 let _mid_bundle = state.take_bundle();
1406
1407 let _ts_is_none = state.transition_state.is_none();
1409
1410 {
1412 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1413 let l2_sto = backing.open_sub_storage(&[1]);
1414 let l2_pricing = super::super::open_l2_pricing_state(l2_sto, 20);
1415
1416 let _backlog_before = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1417
1418 let _ =
1419 l2_pricing.grow_backlog(unsafe { &mut *state_ptr }, 357_751, MultiGas::default());
1420
1421 let _backlog_after = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1422 }
1423
1424 state.merge_transitions(BundleRetention::Reverts);
1426 let mut bundle = state.take_bundle();
1427
1428 let _in_bundle = bundle
1430 .state
1431 .get(&arbos)
1432 .and_then(|a| a.storage.get(&gas_backlog_slot))
1433 .map(|s| s.present_value);
1434
1435 {
1439 let _cache_val = state
1440 .cache
1441 .accounts
1442 .get(&arbos)
1443 .and_then(|ca| ca.account.as_ref())
1444 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1445
1446 if let Some(bundle_acct) = bundle.state.get_mut(&arbos) {
1448 if let Some(cached_acc) = state.cache.accounts.get(&arbos)
1449 && let Some(ref plain) = cached_acc.account
1450 {
1451 for (key, value) in &plain.storage {
1452 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1453 slot.present_value = *value;
1454 } else {
1455 let original =
1456 state.database.storage(arbos, *key).unwrap_or(U256::ZERO);
1457 if *value != original {
1458 bundle_acct.storage.insert(
1459 *key,
1460 StorageSlot {
1461 previous_or_original_value: original,
1462 present_value: *value,
1463 },
1464 );
1465 }
1466 }
1467 }
1468 }
1469 } else {
1470 if let Some(cached_acc) = state.cache.accounts.get(&arbos)
1472 && let Some(ref plain) = cached_acc.account
1473 {
1474 let mut storage_changes: HashMap<U256, StorageSlot> = HashMap::default();
1475 for (key, value) in &plain.storage {
1476 let original = state.database.storage(arbos, *key).unwrap_or(U256::ZERO);
1477 if *value != original {
1478 storage_changes.insert(
1479 *key,
1480 StorageSlot {
1481 previous_or_original_value: original,
1482 present_value: *value,
1483 },
1484 );
1485 }
1486 }
1487 if !storage_changes.is_empty() {
1488 bundle.state.insert(
1489 arbos,
1490 revm::database::BundleAccount {
1491 info: Some(plain.info.clone()),
1492 original_info: None,
1493 storage: storage_changes,
1494 status: revm::database::AccountStatus::Changed,
1495 },
1496 );
1497 }
1498 }
1499 }
1500 }
1501
1502 let _after_augment = bundle
1503 .state
1504 .get(&arbos)
1505 .and_then(|a| a.storage.get(&gas_backlog_slot))
1506 .map(|s| s.present_value);
1507
1508 for (_addr, account) in bundle.state.iter_mut() {
1510 account
1511 .storage
1512 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1513 }
1514
1515 let after_filter = bundle
1516 .state
1517 .get(&arbos)
1518 .and_then(|a| a.storage.get(&gas_backlog_slot))
1519 .map(|s| s.present_value);
1520
1521 assert_eq!(
1522 after_filter,
1523 Some(U256::from(910_507u64)),
1524 "gasBacklog MUST survive even when transition_state was consumed mid-block"
1525 );
1526 }
1527
1528 #[test]
1536 fn test_grow_backlog_survives_evm_commit_and_augment() {
1537 use revm::{
1538 DatabaseCommit,
1539 database::states::{bundle_state::BundleRetention, plain_account::StorageSlot},
1540 };
1541
1542 let l2_base = keccak256([1u8]); let gas_backlog_offset: u64 = 4;
1545 let gas_backlog_slot = arb_storage::storage_key_map(l2_base.as_slice(), gas_backlog_offset);
1546
1547 {
1549 let mut state = StateBuilder::new()
1550 .with_database(EmptyDb)
1551 .with_bundle_update()
1552 .build();
1553
1554 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1556 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1557
1558 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1559
1560 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1562 let l2_sto = backing.open_sub_storage(&[1]);
1563 super::super::initialize_l2_pricing_state(&l2_sto, unsafe { &mut *state_ptr }).unwrap();
1564
1565 let l2_pricing =
1567 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1568 l2_pricing
1569 .set_gas_backlog(unsafe { &mut *state_ptr }, 552756)
1570 .unwrap();
1571 let pre_start = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1572 assert_eq!(pre_start, 552756, "Pre-existing backlog should be 552756");
1573
1574 l2_pricing
1576 .update_pricing_model(unsafe { &mut *state_ptr }, 0, 10)
1577 .unwrap();
1578 let after_start = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1579 assert_eq!(
1580 after_start, 552756,
1581 "time_passed=0 should not change backlog"
1582 );
1583
1584 let empty_state: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1586 Default::default();
1587 state.commit(empty_state);
1588
1589 let _cache_val = state
1591 .cache
1592 .accounts
1593 .get(&ARBOS_STATE_ADDRESS)
1594 .and_then(|ca| ca.account.as_ref())
1595 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1596
1597 let l2_pricing2 =
1599 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1600 l2_pricing2
1601 .grow_backlog(unsafe { &mut *state_ptr }, 357751, MultiGas::default())
1602 .unwrap();
1603 let after_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1604 assert_eq!(after_grow, 552756 + 357751, "backlog should be sum");
1605
1606 let _cache_val2 = state
1608 .cache
1609 .accounts
1610 .get(&ARBOS_STATE_ADDRESS)
1611 .and_then(|ca| ca.account.as_ref())
1612 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1613
1614 state.merge_transitions(BundleRetention::Reverts);
1616 let mut bundle = state.take_bundle();
1617
1618 let _bundle_has_slot = bundle
1620 .state
1621 .get(&ARBOS_STATE_ADDRESS)
1622 .and_then(|a| a.storage.get(&gas_backlog_slot))
1623 .map(|s| s.present_value);
1624
1625 for (addr, cache_acct) in &state.cache.accounts {
1628 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1629 let current_storage = cache_acct
1630 .account
1631 .as_ref()
1632 .map(|a| &a.storage)
1633 .cloned()
1634 .unwrap_or_default();
1635
1636 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1637 bundle_acct.info = current_info;
1638 for (key, value) in ¤t_storage {
1639 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1640 slot.present_value = *value;
1641 } else {
1642 let original_value = U256::ZERO;
1645 if *value != original_value {
1646 bundle_acct.storage.insert(
1647 *key,
1648 StorageSlot {
1649 previous_or_original_value: original_value,
1650 present_value: *value,
1651 },
1652 );
1653 }
1654 }
1655 }
1656 } else {
1657 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
1659 current_storage
1660 .iter()
1661 .filter_map(|(key, value)| {
1662 let original_value = U256::ZERO;
1663 if original_value != *value {
1664 Some((
1665 *key,
1666 StorageSlot {
1667 previous_or_original_value: original_value,
1668 present_value: *value,
1669 },
1670 ))
1671 } else {
1672 None
1673 }
1674 })
1675 .collect();
1676
1677 let info_changed = current_info.is_some(); if info_changed || !storage_changes.is_empty() {
1679 bundle.state.insert(
1680 *addr,
1681 revm::database::BundleAccount {
1682 info: current_info,
1683 original_info: None,
1684 storage: storage_changes,
1685 status: revm::database::AccountStatus::InMemoryChange,
1686 },
1687 );
1688 }
1689 }
1690 }
1691
1692 let _bundle_after_augment = bundle
1693 .state
1694 .get(&ARBOS_STATE_ADDRESS)
1695 .and_then(|a| a.storage.get(&gas_backlog_slot))
1696 .map(|s| (s.present_value, s.previous_or_original_value));
1697
1698 for (_addr, account) in bundle.state.iter_mut() {
1700 account
1701 .storage
1702 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1703 }
1704
1705 let final_slot = bundle
1706 .state
1707 .get(&ARBOS_STATE_ADDRESS)
1708 .and_then(|a| a.storage.get(&gas_backlog_slot))
1709 .map(|s| s.present_value);
1710 assert!(
1711 final_slot.is_some(),
1712 "VARIANT A FAILED: gas_backlog slot MISSING from bundle after empty EVM commit"
1713 );
1714 assert_eq!(
1715 final_slot.unwrap(),
1716 U256::from(552756u64 + 357751u64),
1717 "VARIANT A: gas_backlog should be 910507"
1718 );
1719 }
1720
1721 {
1723 let mut state = StateBuilder::new()
1724 .with_database(EmptyDb)
1725 .with_bundle_update()
1726 .build();
1727
1728 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1729 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1730
1731 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1732
1733 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1734 let l2_sto = backing.open_sub_storage(&[1]);
1735 super::super::initialize_l2_pricing_state(&l2_sto, unsafe { &mut *state_ptr }).unwrap();
1736
1737 let l2_pricing =
1738 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1739 l2_pricing
1740 .set_gas_backlog(unsafe { &mut *state_ptr }, 552756)
1741 .unwrap();
1742 l2_pricing
1743 .update_pricing_model(unsafe { &mut *state_ptr }, 0, 10)
1744 .unwrap();
1745 let _before_commit = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1746
1747 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
1752 let mut arbos_evm_account = revm::state::Account {
1753 info: revm::state::AccountInfo {
1754 balance: U256::ZERO,
1755 nonce: 1,
1756 code_hash: keccak256([]),
1757 code: None,
1758 account_id: None,
1759 },
1760 ..Default::default()
1761 };
1762 arbos_evm_account.mark_touch();
1763 let mut evm_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1765 Default::default();
1766 evm_changes.insert(ARBOS_STATE_ADDRESS, arbos_evm_account);
1767 state.commit(evm_changes);
1768
1769 let l2_pricing2 =
1771 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1772 let _read_before_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1773
1774 l2_pricing2
1775 .grow_backlog(unsafe { &mut *state_ptr }, 357751, MultiGas::default())
1776 .unwrap();
1777 let _after_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1778
1779 let _cache_val2 = state
1781 .cache
1782 .accounts
1783 .get(&ARBOS_STATE_ADDRESS)
1784 .and_then(|ca| ca.account.as_ref())
1785 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
1786
1787 state.merge_transitions(BundleRetention::Reverts);
1789 let mut bundle = state.take_bundle();
1790
1791 let _bundle_pre = bundle
1792 .state
1793 .get(&ARBOS_STATE_ADDRESS)
1794 .and_then(|a| a.storage.get(&gas_backlog_slot))
1795 .map(|s| (s.present_value, s.previous_or_original_value));
1796
1797 for (addr, cache_acct) in &state.cache.accounts {
1799 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1800 let current_storage = cache_acct
1801 .account
1802 .as_ref()
1803 .map(|a| &a.storage)
1804 .cloned()
1805 .unwrap_or_default();
1806
1807 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1808 bundle_acct.info = current_info;
1809 for (key, value) in ¤t_storage {
1810 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1811 slot.present_value = *value;
1812 } else {
1813 let original_value = U256::ZERO;
1814 if *value != original_value {
1815 bundle_acct.storage.insert(
1816 *key,
1817 StorageSlot {
1818 previous_or_original_value: original_value,
1819 present_value: *value,
1820 },
1821 );
1822 }
1823 }
1824 }
1825 }
1826 }
1827
1828 let _bundle_post = bundle
1829 .state
1830 .get(&ARBOS_STATE_ADDRESS)
1831 .and_then(|a| a.storage.get(&gas_backlog_slot))
1832 .map(|s| (s.present_value, s.previous_or_original_value));
1833
1834 for (_addr, account) in bundle.state.iter_mut() {
1836 account
1837 .storage
1838 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1839 }
1840
1841 let final_slot = bundle
1842 .state
1843 .get(&ARBOS_STATE_ADDRESS)
1844 .and_then(|a| a.storage.get(&gas_backlog_slot))
1845 .map(|s| s.present_value);
1846 assert!(
1847 final_slot.is_some(),
1848 "VARIANT B FAILED: gas_backlog slot MISSING from bundle after EVM commit with ArbOS touched"
1849 );
1850 assert_eq!(
1851 final_slot.unwrap(),
1852 U256::from(552756u64 + 357751u64),
1853 "VARIANT B: gas_backlog should be 910507"
1854 );
1855 }
1856
1857 {
1862 let mut state = StateBuilder::new()
1863 .with_database(EmptyDb)
1864 .with_bundle_update()
1865 .build();
1866
1867 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
1868 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
1869
1870 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
1871
1872 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
1873 let l2_sto = backing.open_sub_storage(&[1]);
1874 super::super::initialize_l2_pricing_state(&l2_sto, unsafe { &mut *state_ptr }).unwrap();
1875
1876 let l2_pricing =
1877 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1878 l2_pricing
1879 .set_gas_backlog(unsafe { &mut *state_ptr }, 552756)
1880 .unwrap();
1881 l2_pricing
1882 .update_pricing_model(unsafe { &mut *state_ptr }, 0, 10)
1883 .unwrap();
1884
1885 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
1888 let mut arbos_evm_account = revm::state::Account {
1889 info: revm::state::AccountInfo {
1890 balance: U256::ZERO,
1891 nonce: 1,
1892 code_hash: keccak256([]),
1893 code: None,
1894 account_id: None,
1895 },
1896 ..Default::default()
1897 };
1898 arbos_evm_account.mark_touch();
1899
1900 arbos_evm_account.storage.insert(
1903 gas_backlog_slot,
1904 revm::state::EvmStorageSlot::new(U256::from(552756u64), 0),
1905 );
1907
1908 let mut evm_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
1909 Default::default();
1910 evm_changes.insert(ARBOS_STATE_ADDRESS, arbos_evm_account);
1911 state.commit(evm_changes);
1912
1913 let l2_pricing2 =
1915 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
1916 let _read_before_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1917
1918 l2_pricing2
1919 .grow_backlog(unsafe { &mut *state_ptr }, 357751, MultiGas::default())
1920 .unwrap();
1921 let _after_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
1922
1923 state.merge_transitions(BundleRetention::Reverts);
1925 let mut bundle = state.take_bundle();
1926
1927 let _bundle_pre = bundle
1928 .state
1929 .get(&ARBOS_STATE_ADDRESS)
1930 .and_then(|a| a.storage.get(&gas_backlog_slot))
1931 .map(|s| (s.present_value, s.previous_or_original_value));
1932
1933 for (addr, cache_acct) in &state.cache.accounts {
1935 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
1936 let current_storage = cache_acct
1937 .account
1938 .as_ref()
1939 .map(|a| &a.storage)
1940 .cloned()
1941 .unwrap_or_default();
1942
1943 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
1944 bundle_acct.info = current_info;
1945 for (key, value) in ¤t_storage {
1946 if let Some(slot) = bundle_acct.storage.get_mut(key) {
1947 slot.present_value = *value;
1948 } else {
1949 let original_value = U256::ZERO;
1950 if *value != original_value {
1951 bundle_acct.storage.insert(
1952 *key,
1953 StorageSlot {
1954 previous_or_original_value: original_value,
1955 present_value: *value,
1956 },
1957 );
1958 }
1959 }
1960 }
1961 }
1962 }
1963
1964 let _bundle_post = bundle
1965 .state
1966 .get(&ARBOS_STATE_ADDRESS)
1967 .and_then(|a| a.storage.get(&gas_backlog_slot))
1968 .map(|s| (s.present_value, s.previous_or_original_value));
1969
1970 for (_addr, account) in bundle.state.iter_mut() {
1972 account
1973 .storage
1974 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
1975 }
1976
1977 let final_slot = bundle
1978 .state
1979 .get(&ARBOS_STATE_ADDRESS)
1980 .and_then(|a| a.storage.get(&gas_backlog_slot))
1981 .map(|s| s.present_value);
1982 assert!(
1983 final_slot.is_some(),
1984 "VARIANT C FAILED: gas_backlog slot MISSING from bundle after EVM commit with ArbOS storage read"
1985 );
1986 assert_eq!(
1987 final_slot.unwrap(),
1988 U256::from(552756u64 + 357751u64),
1989 "VARIANT C: gas_backlog should be 910507"
1990 );
1991 }
1992
1993 {
1996 let mut state = StateBuilder::new()
1997 .with_database(EmptyDb)
1998 .with_bundle_update()
1999 .build();
2000
2001 ensure_cache_account(&mut state, ARBOS_STATE_ADDRESS);
2002 arb_storage::set_account_nonce(&mut state, ARBOS_STATE_ADDRESS, 1);
2003
2004 let state_ptr: *mut revm::database::State<EmptyDb> = &mut state;
2005
2006 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
2007 let l2_sto = backing.open_sub_storage(&[1]);
2008 super::super::initialize_l2_pricing_state(&l2_sto, unsafe { &mut *state_ptr }).unwrap();
2009
2010 let l2_pricing =
2012 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2013 l2_pricing
2014 .set_gas_backlog(unsafe { &mut *state_ptr }, 552756)
2015 .unwrap();
2016
2017 l2_pricing
2019 .update_pricing_model(unsafe { &mut *state_ptr }, 0, 10)
2020 .unwrap();
2021
2022 state.commit(Default::default());
2024
2025 let sender = address!("1111111111111111111111111111111111111111");
2027 let receiver = address!("2222222222222222222222222222222222222222");
2028 let _ = state.load_cache_account(sender);
2029 let _ = state.load_cache_account(receiver);
2030
2031 let mut user_changes: alloy_primitives::map::HashMap<Address, revm::state::Account> =
2032 Default::default();
2033 let mut sender_acct = revm::state::Account::default();
2034 sender_acct.info.balance = U256::from(999_000u64);
2035 sender_acct.info.nonce = 1;
2036 sender_acct.mark_touch();
2037 user_changes.insert(sender, sender_acct);
2038
2039 let mut receiver_acct = revm::state::Account::default();
2040 receiver_acct.info.balance = U256::from(1_000u64);
2041 receiver_acct.mark_touch();
2042 user_changes.insert(receiver, receiver_acct);
2043
2044 state.commit(user_changes);
2045
2046 let _cache_val_after_user = state
2048 .cache
2049 .accounts
2050 .get(&ARBOS_STATE_ADDRESS)
2051 .and_then(|ca| ca.account.as_ref())
2052 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
2053
2054 let l2_pricing2 =
2057 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2058 let _read_val = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2059 l2_pricing2
2060 .grow_backlog(unsafe { &mut *state_ptr }, 357751, MultiGas::default())
2061 .unwrap();
2062 let after_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2063 assert_eq!(after_grow, 552756 + 357751, "backlog should be sum");
2064
2065 state.merge_transitions(BundleRetention::Reverts);
2067 let mut bundle = state.take_bundle();
2068
2069 let _bundle_pre = bundle
2070 .state
2071 .get(&ARBOS_STATE_ADDRESS)
2072 .and_then(|a| a.storage.get(&gas_backlog_slot))
2073 .map(|s| (s.present_value, s.previous_or_original_value));
2074
2075 for (addr, cache_acct) in &state.cache.accounts {
2077 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2078 let current_storage = cache_acct
2079 .account
2080 .as_ref()
2081 .map(|a| &a.storage)
2082 .cloned()
2083 .unwrap_or_default();
2084
2085 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2086 bundle_acct.info = current_info;
2087 for (key, value) in ¤t_storage {
2088 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2089 slot.present_value = *value;
2090 } else {
2091 let original_value = U256::ZERO;
2092 if *value != original_value {
2093 bundle_acct.storage.insert(
2094 *key,
2095 StorageSlot {
2096 previous_or_original_value: original_value,
2097 present_value: *value,
2098 },
2099 );
2100 }
2101 }
2102 }
2103 } else {
2104 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
2105 current_storage
2106 .iter()
2107 .filter_map(|(key, value)| {
2108 let original_value = U256::ZERO;
2109 if original_value != *value {
2110 Some((
2111 *key,
2112 StorageSlot {
2113 previous_or_original_value: original_value,
2114 present_value: *value,
2115 },
2116 ))
2117 } else {
2118 None
2119 }
2120 })
2121 .collect();
2122 let info_changed = current_info.is_some();
2123 if info_changed || !storage_changes.is_empty() {
2124 bundle.state.insert(
2125 *addr,
2126 revm::database::BundleAccount {
2127 info: current_info,
2128 original_info: None,
2129 storage: storage_changes,
2130 status: revm::database::AccountStatus::InMemoryChange,
2131 },
2132 );
2133 }
2134 }
2135 }
2136
2137 let _bundle_post = bundle
2138 .state
2139 .get(&ARBOS_STATE_ADDRESS)
2140 .and_then(|a| a.storage.get(&gas_backlog_slot))
2141 .map(|s| (s.present_value, s.previous_or_original_value));
2142
2143 for (_addr, account) in bundle.state.iter_mut() {
2145 account
2146 .storage
2147 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2148 }
2149
2150 let final_slot = bundle
2151 .state
2152 .get(&ARBOS_STATE_ADDRESS)
2153 .and_then(|a| a.storage.get(&gas_backlog_slot))
2154 .map(|s| s.present_value);
2155 assert!(
2156 final_slot.is_some(),
2157 "VARIANT D FAILED: gas_backlog slot MISSING from bundle"
2158 );
2159 assert_eq!(
2160 final_slot.unwrap(),
2161 U256::from(552756u64 + 357751u64),
2162 "VARIANT D: gas_backlog should be 910507"
2163 );
2164 }
2165
2166 {
2172 struct PrePopulatedDb {
2174 gas_backlog_slot: U256,
2175 pre_existing_backlog: U256,
2176 }
2177
2178 impl Database for PrePopulatedDb {
2179 type Error = std::convert::Infallible;
2180 fn basic(
2181 &mut self,
2182 _address: Address,
2183 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2184 Ok(Some(revm::state::AccountInfo {
2186 balance: U256::ZERO,
2187 nonce: 1,
2188 code_hash: keccak256([]),
2189 code: None,
2190 account_id: None,
2191 }))
2192 }
2193 fn code_by_hash(
2194 &mut self,
2195 _code_hash: B256,
2196 ) -> Result<revm::state::Bytecode, Self::Error> {
2197 Ok(revm::state::Bytecode::default())
2198 }
2199 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2200 if index == self.gas_backlog_slot {
2202 Ok(self.pre_existing_backlog)
2203 } else {
2204 Ok(U256::ZERO)
2205 }
2206 }
2207 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2208 Ok(B256::ZERO)
2209 }
2210 }
2211
2212 let pre_existing_backlog = U256::from(552756u64);
2213 let mut state = StateBuilder::new()
2214 .with_database(PrePopulatedDb {
2215 gas_backlog_slot,
2216 pre_existing_backlog,
2217 })
2218 .with_bundle_update()
2219 .build();
2220
2221 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2223
2224 let state_ptr: *mut revm::database::State<PrePopulatedDb> = &mut state;
2225
2226 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
2228 let l2_pricing =
2229 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2230
2231 let current = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2233 assert_eq!(current, 552756, "Should read from DB");
2234
2235 l2_pricing
2237 .update_pricing_model(unsafe { &mut *state_ptr }, 0, 10)
2238 .unwrap();
2239 let _after_start = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2240
2241 {
2243 use revm::DatabaseCommit;
2244 let empty: alloy_primitives::map::HashMap<Address, revm::state::Account> =
2245 Default::default();
2246 state.commit(empty);
2247 }
2248
2249 {
2251 use revm::DatabaseCommit;
2252 let sender = address!("1111111111111111111111111111111111111111");
2253 let _ = state.load_cache_account(sender);
2254 let mut user_changes: alloy_primitives::map::HashMap<
2255 Address,
2256 revm::state::Account,
2257 > = Default::default();
2258 let mut sender_acct = revm::state::Account::default();
2259 sender_acct.info.balance = U256::from(999_000u64);
2260 sender_acct.info.nonce = 1;
2261 sender_acct.mark_touch();
2262 user_changes.insert(sender, sender_acct);
2263 state.commit(user_changes);
2264 }
2265
2266 let l2_pricing2 =
2268 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2269 let _read_before = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2270
2271 l2_pricing2
2272 .grow_backlog(unsafe { &mut *state_ptr }, 357751, MultiGas::default())
2273 .unwrap();
2274 let _after_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2275
2276 let _cache_val = state
2278 .cache
2279 .accounts
2280 .get(&ARBOS_STATE_ADDRESS)
2281 .and_then(|ca| ca.account.as_ref())
2282 .and_then(|a| a.storage.get(&gas_backlog_slot).copied());
2283
2284 state.merge_transitions(BundleRetention::Reverts);
2286 let mut bundle = state.take_bundle();
2287
2288 let _bundle_pre = bundle
2289 .state
2290 .get(&ARBOS_STATE_ADDRESS)
2291 .and_then(|a| a.storage.get(&gas_backlog_slot))
2292 .map(|s| (s.present_value, s.previous_or_original_value));
2293
2294 for (addr, cache_acct) in &state.cache.accounts {
2296 let current_info = cache_acct.account.as_ref().map(|a| a.info.clone());
2297 let current_storage = cache_acct
2298 .account
2299 .as_ref()
2300 .map(|a| &a.storage)
2301 .cloned()
2302 .unwrap_or_default();
2303
2304 if let Some(bundle_acct) = bundle.state.get_mut(addr) {
2305 bundle_acct.info = current_info;
2306 for (key, value) in ¤t_storage {
2307 if let Some(slot) = bundle_acct.storage.get_mut(key) {
2308 slot.present_value = *value;
2309 } else {
2310 let original_value =
2312 if *addr == ARBOS_STATE_ADDRESS && *key == gas_backlog_slot {
2313 pre_existing_backlog
2314 } else {
2315 U256::ZERO
2316 };
2317 if *value != original_value {
2318 bundle_acct.storage.insert(
2319 *key,
2320 StorageSlot {
2321 previous_or_original_value: original_value,
2322 present_value: *value,
2323 },
2324 );
2325 }
2326 }
2327 }
2328 } else {
2329 let storage_changes: alloy_primitives::map::HashMap<U256, StorageSlot> =
2331 current_storage
2332 .iter()
2333 .filter_map(|(key, value)| {
2334 let original_value =
2335 if *addr == ARBOS_STATE_ADDRESS && *key == gas_backlog_slot {
2336 pre_existing_backlog
2337 } else {
2338 U256::ZERO
2339 };
2340 if original_value != *value {
2341 Some((
2342 *key,
2343 StorageSlot {
2344 previous_or_original_value: original_value,
2345 present_value: *value,
2346 },
2347 ))
2348 } else {
2349 None
2350 }
2351 })
2352 .collect();
2353 let info_changed = false; if info_changed || !storage_changes.is_empty() {
2355 bundle.state.insert(
2356 *addr,
2357 revm::database::BundleAccount {
2358 info: current_info,
2359 original_info: None,
2360 storage: storage_changes,
2361 status: revm::database::AccountStatus::Changed,
2362 },
2363 );
2364 }
2365 }
2366 }
2367
2368 let _bundle_post = bundle
2369 .state
2370 .get(&ARBOS_STATE_ADDRESS)
2371 .and_then(|a| a.storage.get(&gas_backlog_slot))
2372 .map(|s| (s.present_value, s.previous_or_original_value));
2373
2374 for (_addr, account) in bundle.state.iter_mut() {
2376 account
2377 .storage
2378 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2379 }
2380
2381 let final_slot = bundle
2382 .state
2383 .get(&ARBOS_STATE_ADDRESS)
2384 .and_then(|a| a.storage.get(&gas_backlog_slot))
2385 .map(|s| s.present_value);
2386 assert!(
2387 final_slot.is_some(),
2388 "VARIANT E FAILED: gas_backlog slot MISSING from bundle (pre-populated DB)"
2389 );
2390 assert_eq!(
2391 final_slot.unwrap(),
2392 U256::from(552756u64 + 357751u64),
2393 "VARIANT E: gas_backlog should be 910507"
2394 );
2395 }
2396
2397 {
2407 struct PrePopulatedDb2 {
2408 gas_backlog_slot: U256,
2409 pre_existing_backlog: U256,
2410 speed_limit_slot: U256,
2411 speed_limit_value: U256,
2412 }
2413
2414 impl Database for PrePopulatedDb2 {
2415 type Error = std::convert::Infallible;
2416 fn basic(
2417 &mut self,
2418 _address: Address,
2419 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2420 Ok(Some(revm::state::AccountInfo {
2421 balance: U256::ZERO,
2422 nonce: 1,
2423 code_hash: keccak256([]),
2424 code: None,
2425 account_id: None,
2426 }))
2427 }
2428 fn code_by_hash(
2429 &mut self,
2430 _code_hash: B256,
2431 ) -> Result<revm::state::Bytecode, Self::Error> {
2432 Ok(revm::state::Bytecode::default())
2433 }
2434 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2435 if index == self.gas_backlog_slot {
2436 Ok(self.pre_existing_backlog)
2437 } else if index == self.speed_limit_slot {
2438 Ok(self.speed_limit_value)
2439 } else {
2440 Ok(U256::ZERO)
2441 }
2442 }
2443 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2444 Ok(B256::ZERO)
2445 }
2446 }
2447
2448 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0); let pre_existing_backlog = U256::from(552756u64);
2451
2452 let mut state = StateBuilder::new()
2453 .with_database(PrePopulatedDb2 {
2454 gas_backlog_slot,
2455 pre_existing_backlog,
2456 speed_limit_slot,
2457 speed_limit_value: U256::from(7_000_000u64), })
2459 .with_bundle_update()
2460 .build();
2461
2462 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2463 let state_ptr: *mut revm::database::State<PrePopulatedDb2> = &mut state;
2464
2465 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
2466 let l2_pricing =
2467 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2468
2469 let _initial = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2470
2471 l2_pricing
2474 .update_pricing_model(unsafe { &mut *state_ptr }, 1, 10)
2475 .unwrap();
2476 let _after_drain = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2477
2478 {
2480 use revm::DatabaseCommit;
2481 state.commit(Default::default());
2482 }
2483
2484 let l2_pricing2 =
2486 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2487 l2_pricing2
2488 .grow_backlog(unsafe { &mut *state_ptr }, 357751, MultiGas::default())
2489 .unwrap();
2490 let _after_grow = l2_pricing2.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2491
2492 state.merge_transitions(BundleRetention::Reverts);
2494 let mut bundle = state.take_bundle();
2495
2496 let _bundle_pre = bundle
2497 .state
2498 .get(&ARBOS_STATE_ADDRESS)
2499 .and_then(|a| a.storage.get(&gas_backlog_slot))
2500 .map(|s| (s.present_value, s.previous_or_original_value));
2501
2502 for (_addr, account) in bundle.state.iter_mut() {
2508 account
2509 .storage
2510 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2511 }
2512
2513 let final_slot = bundle
2514 .state
2515 .get(&ARBOS_STATE_ADDRESS)
2516 .and_then(|a| a.storage.get(&gas_backlog_slot))
2517 .map(|s| (s.present_value, s.previous_or_original_value));
2518
2519 assert!(
2523 final_slot.is_some(),
2524 "VARIANT F FAILED: gas_backlog slot MISSING from bundle (drain+grow)"
2525 );
2526 assert_eq!(
2527 final_slot.unwrap().0,
2528 U256::from(357751u64),
2529 "VARIANT F: gas_backlog should be 357751"
2530 );
2531 }
2532
2533 {
2542 struct PrePopDb3 {
2543 gas_backlog_slot: U256,
2544 speed_limit_slot: U256,
2545 }
2546
2547 impl Database for PrePopDb3 {
2548 type Error = std::convert::Infallible;
2549 fn basic(
2550 &mut self,
2551 _address: Address,
2552 ) -> Result<Option<revm::state::AccountInfo>, Self::Error> {
2553 Ok(Some(revm::state::AccountInfo {
2554 balance: U256::ZERO,
2555 nonce: 1,
2556 code_hash: keccak256([]),
2557 code: None,
2558 account_id: None,
2559 }))
2560 }
2561 fn code_by_hash(
2562 &mut self,
2563 _code_hash: B256,
2564 ) -> Result<revm::state::Bytecode, Self::Error> {
2565 Ok(revm::state::Bytecode::default())
2566 }
2567 fn storage(&mut self, _address: Address, index: U256) -> Result<U256, Self::Error> {
2568 if index == self.gas_backlog_slot {
2569 Ok(U256::from(552756u64))
2570 } else if index == self.speed_limit_slot {
2571 Ok(U256::from(7_000_000u64))
2572 } else {
2573 Ok(U256::ZERO)
2574 }
2575 }
2576 fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> {
2577 Ok(B256::ZERO)
2578 }
2579 }
2580
2581 let speed_limit_slot = arb_storage::storage_key_map(l2_base.as_slice(), 0);
2582
2583 let mut state = StateBuilder::new()
2584 .with_database(PrePopDb3 {
2585 gas_backlog_slot,
2586 speed_limit_slot,
2587 })
2588 .with_bundle_update()
2589 .build();
2590
2591 let _ = state.load_cache_account(ARBOS_STATE_ADDRESS);
2592 let state_ptr: *mut revm::database::State<PrePopDb3> = &mut state;
2593
2594 let backing = Storage::new(unsafe { &mut *state_ptr }, B256::ZERO);
2595 let l2_pricing =
2596 super::super::open_l2_pricing_state(backing.open_sub_storage(&[1]), 10);
2597
2598 let _initial = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2599
2600 l2_pricing
2602 .update_pricing_model(unsafe { &mut *state_ptr }, 1, 10)
2603 .unwrap();
2604 let after_drain = l2_pricing.gas_backlog(unsafe { &mut *state_ptr }).unwrap();
2605 assert_eq!(after_drain, 0);
2606
2607 state.merge_transitions(BundleRetention::Reverts);
2609 let mut bundle = state.take_bundle();
2610
2611 let _pre_filter = bundle
2612 .state
2613 .get(&ARBOS_STATE_ADDRESS)
2614 .and_then(|a| a.storage.get(&gas_backlog_slot))
2615 .map(|s| (s.present_value, s.previous_or_original_value));
2616
2617 for (_addr, account) in bundle.state.iter_mut() {
2619 account
2620 .storage
2621 .retain(|_key, slot| slot.present_value != slot.previous_or_original_value);
2622 }
2623
2624 let final_slot = 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
2630 assert!(
2632 final_slot.is_some(),
2633 "VARIANT G FAILED: drain-to-0 write was lost!"
2634 );
2635 }
2636 }
2637}