1use std::collections::BTreeMap;
2
3use primitive_types::{H160, U256};
4use zk_evm_abstractions::{aux::Timestamp, queries::LogQuery};
5use zkevm_opcode_defs::system_params::{
6 STORAGE_ACCESS_COLD_READ_COST, STORAGE_ACCESS_COLD_WRITE_COST, STORAGE_ACCESS_WARM_READ_COST,
7 STORAGE_ACCESS_WARM_WRITE_COST, STORAGE_AUX_BYTE,
8};
9use zksync_vm2_interface::{CycleStats, Event, HeapId, L2ToL1Log, Tracer};
10
11use crate::{
12 rollback::{Rollback, RollbackableLog, RollbackableMap, RollbackablePod, RollbackableSet},
13 StorageInterface, StorageSlot,
14};
15
16#[derive(Debug, Clone, Copy, Default)]
19pub struct StorageWriteEntry {
20 pub value: U256,
22 pub paid: u32,
24}
25
26const SLOT_READ: u8 = 1;
29const SLOT_WRITTEN: u8 = 1 << 1;
30const SLOT_PROTECTIVE_READ: u8 = 1 << 2;
35
36#[derive(Debug, Default)]
39pub struct WorldDiff {
40 storage_writes: RollbackableMap<(H160, U256), StorageWriteEntry>,
44 transient_storage_changes: RollbackableMap<(H160, U256), U256>,
45 events: RollbackableLog<Event>,
46 l2_to_l1_logs: RollbackableLog<L2ToL1Log>,
47 pub(crate) pubdata: RollbackablePod<i32>,
48 storage_refunds: RollbackableLog<u32>,
49 pubdata_costs: RollbackableLog<i32>,
50 storage_logs: Vec<LogQuery>,
51 rollback_storage_logs: Vec<LogQuery>,
52 pub(crate) decommitted_hashes: RollbackableMap<U256, DecommitState>,
61 decommit_pinned_pages: RollbackableSet<u32>,
68 slot_flags: RollbackableMap<(H160, U256), u8>,
77
78 storage_initial_values: BTreeMap<(H160, U256), StorageSlot>,
80
81 skip_storage_logs: bool,
92}
93
94#[derive(Debug)]
95pub(crate) struct ExternalSnapshot {
96 internal_snapshot: Snapshot,
97 pub(crate) decommitted_hashes: <RollbackableMap<U256, DecommitState> as Rollback>::Snapshot,
98 decommit_pinned_pages: <RollbackableSet<u32> as Rollback>::Snapshot,
99 slot_flags: <RollbackableMap<(H160, U256), u8> as Rollback>::Snapshot,
100 storage_refunds: <RollbackableLog<u32> as Rollback>::Snapshot,
101 pubdata_costs: <RollbackableLog<i32> as Rollback>::Snapshot,
102}
103
104#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
105pub(crate) enum DecommitState {
106 #[default]
115 Unsuccessful,
116 Succeeded(u32),
118}
119
120impl WorldDiff {
121 pub fn set_record_storage_logs(&mut self, record: bool) {
136 assert!(
137 self.storage_logs.is_empty()
138 && self.storage_writes.as_ref().is_empty()
139 && self.slot_flags.as_ref().is_empty()
140 && self.storage_initial_values.is_empty(),
141 "set_record_storage_logs must be called before any storage access"
142 );
143 self.skip_storage_logs = !record;
144 }
145
146 pub fn reserve_auxiliary_log_capacity(
150 &mut self,
151 events: usize,
152 pubdata_costs: usize,
153 storage_refunds: usize,
154 ) {
155 self.events.reserve(events);
156 self.pubdata_costs.reserve(pubdata_costs);
157 self.storage_refunds.reserve(storage_refunds);
158 }
159
160 fn slot_add_flag(&mut self, key: (H160, U256), flag: u8) -> bool {
165 self.slot_flags.add_flags(key, flag)
166 }
167
168 pub(crate) fn read_storage(
170 &mut self,
171 world: &mut impl StorageInterface,
172 tracer: &mut impl Tracer,
173 contract: H160,
174 key: U256,
175 tx_number_in_block: u16,
176 ) -> (U256, u32) {
177 let (value, newly_added) =
178 self.read_storage_inner(world, tracer, contract, key, tx_number_in_block);
179 let refund = if !newly_added || world.is_free_storage_slot(&contract, &key) {
180 WARM_READ_REFUND
181 } else {
182 0
183 };
184 self.storage_refunds.push(refund);
185 (value, refund)
186 }
187
188 pub(crate) fn read_storage_without_refund(
192 &mut self,
193 world: &mut impl StorageInterface,
194 tracer: &mut impl Tracer,
195 contract: H160,
196 key: U256,
197 tx_number_in_block: u16,
198 ) -> U256 {
199 self.read_storage_inner(world, tracer, contract, key, tx_number_in_block)
200 .0
201 }
202
203 fn read_storage_inner(
204 &mut self,
205 world: &mut impl StorageInterface,
206 tracer: &mut impl Tracer,
207 contract: H160,
208 key: U256,
209 tx_number_in_block: u16,
210 ) -> (U256, bool) {
211 let newly_added = self.slot_add_flag((contract, key), SLOT_READ);
212 if newly_added {
213 tracer.on_extra_prover_cycles(CycleStats::StorageRead);
214 }
215
216 self.pubdata_costs.push(0);
217 let value = if self.skip_storage_logs {
218 let live_write = self
220 .storage_writes
221 .as_ref()
222 .get(&(contract, key))
223 .map(|e| e.value);
224 if let Some(value) = live_write {
225 value
226 } else {
227 let initial_value = self
230 .storage_initial_values
231 .entry((contract, key))
232 .or_insert_with(|| world.read_storage(contract, key))
233 .value;
234 self.slot_add_flag((contract, key), SLOT_PROTECTIVE_READ);
235 initial_value
236 }
237 } else {
238 let value = self.just_read_storage(world, contract, key);
241 self.storage_logs.push(LogQuery {
246 timestamp: Timestamp(
247 u32::try_from(self.storage_logs.len()).expect("Too many storage logs"),
248 ),
249 tx_number_in_block,
250 aux_byte: STORAGE_AUX_BYTE,
251 shard_id: 0,
252 address: contract,
253 key,
254 read_value: value,
255 written_value: value,
256 rw_flag: false,
257 rollback: false,
258 is_service: false,
259 });
260 value
261 };
262 (value, newly_added)
263 }
264
265 pub(crate) fn just_read_storage(
268 &self,
269 world: &mut impl StorageInterface,
270 contract: H160,
271 key: U256,
272 ) -> U256 {
273 self.storage_writes
274 .as_ref()
275 .get(&(contract, key))
276 .map_or_else(|| world.read_storage_value(contract, key), |e| e.value)
277 }
278
279 pub(crate) fn write_storage(
281 &mut self,
282 world: &mut impl StorageInterface,
283 tracer: &mut impl Tracer,
284 contract: H160,
285 key: U256,
286 value: U256,
287 tx_number_in_block: u16,
288 ) -> u32 {
289 if !self.skip_storage_logs {
290 let read_value = self.just_read_storage(world, contract, key);
294 let log_query = LogQuery {
295 timestamp: Timestamp(u32::try_from(self.storage_logs.len()).unwrap_or(u32::MAX)),
296 tx_number_in_block,
297 aux_byte: STORAGE_AUX_BYTE,
298 shard_id: 0,
299 address: contract,
300 key,
301 read_value,
302 written_value: value,
303 rw_flag: true,
304 rollback: false,
305 is_service: false,
306 };
307 self.storage_logs.push(log_query);
308 self.rollback_storage_logs.push(LogQuery {
309 rollback: true,
310 ..log_query
311 });
312 }
313 let initial_value = self
314 .storage_initial_values
315 .entry((contract, key))
316 .or_insert_with(|| world.read_storage(contract, key));
317
318 if world.is_free_storage_slot(&contract, &key) {
319 self.storage_writes
323 .update((contract, key), |prev| StorageWriteEntry {
324 value,
325 paid: prev.map_or(0, |e| e.paid),
326 });
327 if self.slot_add_flag((contract, key), SLOT_WRITTEN) {
328 tracer.on_extra_prover_cycles(CycleStats::StorageWrite);
329 }
330 self.slot_add_flag((contract, key), SLOT_READ);
331
332 self.storage_refunds.push(WARM_WRITE_REFUND);
333 self.pubdata_costs.push(0);
334 return WARM_WRITE_REFUND;
335 }
336
337 let update_cost = world.cost_of_writing_storage(*initial_value, value);
338 let prepaid = self
341 .storage_writes
342 .insert(
343 (contract, key),
344 StorageWriteEntry {
345 value,
346 paid: update_cost,
347 },
348 )
349 .map_or(0, |e| e.paid);
350
351 let refund = if self.slot_add_flag((contract, key), SLOT_WRITTEN) {
352 tracer.on_extra_prover_cycles(CycleStats::StorageWrite);
353
354 if self.slot_add_flag((contract, key), SLOT_READ) {
355 0
356 } else {
357 COLD_WRITE_AFTER_WARM_READ_REFUND
358 }
359 } else {
360 WARM_WRITE_REFUND
361 };
362
363 #[allow(clippy::cast_possible_wrap)]
364 {
365 let pubdata_cost = (update_cost as i32) - (prepaid as i32);
366 self.pubdata.0 += pubdata_cost;
367 self.storage_refunds.push(refund);
368 self.pubdata_costs.push(pubdata_cost);
369 }
370 refund
371 }
372
373 pub(crate) fn pubdata(&self) -> i32 {
374 self.pubdata.0
375 }
376
377 pub fn storage_refunds(&self) -> &[u32] {
379 self.storage_refunds.as_ref()
380 }
381
382 pub fn pubdata_costs(&self) -> &[i32] {
384 self.pubdata_costs.as_ref()
385 }
386
387 pub fn protective_reads_iter(&self) -> impl Iterator<Item = (H160, U256)> + '_ {
393 self.slot_flags
394 .as_ref()
395 .iter()
396 .filter(|(_, f)| **f & SLOT_PROTECTIVE_READ != 0)
397 .map(|(k, _)| *k)
398 }
399
400 pub fn initial_storage_value(&self, contract: H160, key: U256) -> Option<crate::StorageSlot> {
409 self.storage_initial_values.get(&(contract, key)).copied()
410 }
411
412 pub fn storage_log_queries(&self) -> &[LogQuery] {
419 &self.storage_logs
420 }
421
422 pub fn storage_log_queries_after(&self, snapshot: &Snapshot) -> &[LogQuery] {
424 &self.storage_logs[snapshot.storage_logs_len..]
425 }
426
427 #[doc(hidden)] pub fn get_storage_state(&self) -> &BTreeMap<(H160, U256), StorageWriteEntry> {
429 self.storage_writes.as_ref()
430 }
431
432 pub fn get_storage_changes(&self) -> impl Iterator<Item = ((H160, U256), StorageChange)> + '_ {
434 self.storage_writes
435 .as_ref()
436 .iter()
437 .filter_map(|(key, entry)| {
438 let initial_slot = &self.storage_initial_values[key];
439 if initial_slot.value == entry.value {
440 None
441 } else {
442 Some((
443 *key,
444 StorageChange {
445 before: initial_slot.value,
446 after: entry.value,
447 is_initial: initial_slot.is_write_initial,
448 },
449 ))
450 }
451 })
452 }
453
454 pub fn get_storage_changes_after(
456 &self,
457 snapshot: &Snapshot,
458 ) -> impl Iterator<Item = ((H160, U256), StorageChange)> + '_ {
459 self.storage_writes
460 .changes_after(snapshot.storage_writes)
461 .into_iter()
462 .map(|(key, (before, after))| {
463 let initial = self.storage_initial_values[&key];
464 (
465 key,
466 StorageChange {
467 before: before.map_or(initial.value, |e| e.value),
468 after: after.value,
469 is_initial: initial.is_write_initial,
470 },
471 )
472 })
473 }
474
475 pub(crate) fn read_transient_storage(&mut self, contract: H160, key: U256) -> U256 {
476 self.pubdata_costs.push(0);
477 self.transient_storage_changes
478 .as_ref()
479 .get(&(contract, key))
480 .copied()
481 .unwrap_or_default()
482 }
483
484 pub(crate) fn write_transient_storage(&mut self, contract: H160, key: U256, value: U256) {
485 self.pubdata_costs.push(0);
486 self.transient_storage_changes
487 .insert((contract, key), value);
488 }
489
490 pub(crate) fn get_transient_storage_state(&self) -> &BTreeMap<(H160, U256), U256> {
491 self.transient_storage_changes.as_ref()
492 }
493
494 pub(crate) fn record_event(&mut self, event: Event) {
495 self.events.push(event);
496 }
497
498 pub(crate) fn events(&self) -> &[Event] {
499 self.events.as_ref()
500 }
501
502 pub fn events_after(&self, snapshot: &Snapshot) -> &[Event] {
504 self.events.logs_after(snapshot.events)
505 }
506
507 pub(crate) fn record_l2_to_l1_log(&mut self, log: L2ToL1Log) {
508 self.l2_to_l1_logs.push(log);
509 }
510
511 pub(crate) fn l2_to_l1_logs(&self) -> &[L2ToL1Log] {
512 self.l2_to_l1_logs.as_ref()
513 }
514
515 pub fn l2_to_l1_logs_after(&self, snapshot: &Snapshot) -> &[L2ToL1Log] {
517 self.l2_to_l1_logs.logs_after(snapshot.l2_to_l1_logs)
518 }
519
520 pub fn decommitted_hashes(&self) -> impl Iterator<Item = U256> + '_ {
526 self.decommitted_hashes.as_ref().keys().copied()
527 }
528
529 pub(crate) fn decommit_page(&self, code_hash: U256) -> Option<HeapId> {
530 self.decommitted_hashes
531 .as_ref()
532 .get(&code_hash)
533 .and_then(|state| {
534 if let DecommitState::Succeeded(page) = state {
535 Some(HeapId::from_u32_unchecked(*page))
536 } else {
537 None
538 }
539 })
540 }
541
542 pub(crate) fn is_decommit_page_pinned(&self, page: HeapId) -> bool {
543 self.decommit_pinned_pages.as_ref().contains(&page.as_u32())
544 }
545
546 pub(crate) fn set_decommit_page(&mut self, code_hash: U256, page: HeapId) {
547 self.decommitted_hashes
548 .insert(code_hash, DecommitState::Succeeded(page.as_u32()));
549 self.decommit_pinned_pages.add(page.as_u32());
550 }
551
552 pub fn snapshot(&self) -> Snapshot {
554 Snapshot {
555 storage_writes: self.storage_writes.snapshot(),
556 events: self.events.snapshot(),
557 l2_to_l1_logs: self.l2_to_l1_logs.snapshot(),
558 transient_storage_changes: self.transient_storage_changes.snapshot(),
559 pubdata: self.pubdata.snapshot(),
560 storage_logs_len: self.storage_logs.len(),
561 rollback_storage_logs_len: self.rollback_storage_logs.len(),
562 }
563 }
564
565 pub(crate) fn append_rollback_logs(&mut self, snapshot: &Snapshot) {
570 if self.rollback_storage_logs.len() > snapshot.rollback_storage_logs_len {
571 let rollback_logs = self
572 .rollback_storage_logs
573 .split_off(snapshot.rollback_storage_logs_len);
574 for log in rollback_logs.into_iter().rev() {
575 self.storage_logs.push(log);
576 }
577 }
578 }
579
580 #[allow(clippy::needless_pass_by_value)] pub(crate) fn rollback(&mut self, snapshot: Snapshot) {
582 self.storage_writes.rollback(snapshot.storage_writes);
583 self.events.rollback(snapshot.events);
584 self.l2_to_l1_logs.rollback(snapshot.l2_to_l1_logs);
585 self.transient_storage_changes
586 .rollback(snapshot.transient_storage_changes);
587 self.pubdata.rollback(snapshot.pubdata);
588 }
589
590 pub(crate) fn external_snapshot(&self) -> ExternalSnapshot {
593 ExternalSnapshot {
598 internal_snapshot: Snapshot {
599 transient_storage_changes: 0,
600 ..self.snapshot()
601 },
602 decommitted_hashes: self.decommitted_hashes.snapshot(),
603 decommit_pinned_pages: self.decommit_pinned_pages.snapshot(),
604 slot_flags: self.slot_flags.snapshot(),
605 storage_refunds: self.storage_refunds.snapshot(),
606 pubdata_costs: self.pubdata_costs.snapshot(),
607 }
608 }
609
610 pub(crate) fn external_rollback(&mut self, snapshot: ExternalSnapshot) {
611 let storage_logs_len = snapshot.internal_snapshot.storage_logs_len;
612 let rollback_storage_logs_len = snapshot.internal_snapshot.rollback_storage_logs_len;
613
614 self.rollback(snapshot.internal_snapshot);
615 self.storage_refunds.rollback(snapshot.storage_refunds);
616 self.pubdata_costs.rollback(snapshot.pubdata_costs);
617 self.decommitted_hashes
618 .rollback(snapshot.decommitted_hashes);
619 self.decommit_pinned_pages
620 .rollback(snapshot.decommit_pinned_pages);
621 self.slot_flags.rollback(snapshot.slot_flags);
622 self.storage_logs.truncate(storage_logs_len);
623 self.rollback_storage_logs
624 .truncate(rollback_storage_logs_len);
625 }
626
627 pub(crate) fn delete_history(&mut self) {
628 self.storage_writes.delete_history();
629 self.transient_storage_changes.delete_history();
630 self.events.delete_history();
631 self.l2_to_l1_logs.delete_history();
632 self.pubdata.delete_history();
633 self.storage_refunds.delete_history();
634 self.pubdata_costs.delete_history();
635 self.decommitted_hashes.delete_history();
636 self.decommit_pinned_pages.delete_history();
637 self.slot_flags.delete_history();
638 }
639
640 pub(crate) fn clear_transient_storage(&mut self) {
641 self.transient_storage_changes = RollbackableMap::default();
642 }
643}
644
645#[derive(Clone, PartialEq, Debug)]
648pub struct Snapshot {
649 storage_writes: <RollbackableMap<(H160, U256), StorageWriteEntry> as Rollback>::Snapshot,
650 events: <RollbackableLog<Event> as Rollback>::Snapshot,
651 l2_to_l1_logs: <RollbackableLog<L2ToL1Log> as Rollback>::Snapshot,
652 transient_storage_changes: <RollbackableMap<(H160, U256), U256> as Rollback>::Snapshot,
653 pubdata: <RollbackablePod<i32> as Rollback>::Snapshot,
654 storage_logs_len: usize,
655 rollback_storage_logs_len: usize,
656}
657
658#[derive(Debug, PartialEq)]
660pub struct StorageChange {
661 pub before: U256,
663 pub after: U256,
665 pub is_initial: bool,
668}
669
670const WARM_READ_REFUND: u32 = STORAGE_ACCESS_COLD_READ_COST - STORAGE_ACCESS_WARM_READ_COST;
671const WARM_WRITE_REFUND: u32 = STORAGE_ACCESS_COLD_WRITE_COST - STORAGE_ACCESS_WARM_WRITE_COST;
672const COLD_WRITE_AFTER_WARM_READ_REFUND: u32 = STORAGE_ACCESS_COLD_READ_COST;
673
674#[cfg(test)]
675mod tests {
676 use std::collections::BTreeSet;
677
678 use proptest::{bits, collection::btree_map, prelude::*};
679
680 use super::*;
681 use crate::StorageSlot;
682
683 fn test_storage_changes(
684 initial_values: &BTreeMap<(H160, U256), StorageSlot>,
685 first_changes: BTreeMap<(H160, U256), U256>,
686 second_changes: BTreeMap<(H160, U256), U256>,
687 ) {
688 let mut world_diff = WorldDiff {
689 storage_initial_values: initial_values.clone(),
690 ..WorldDiff::default()
691 };
692
693 let checkpoint1 = world_diff.snapshot();
694 for (key, value) in &first_changes {
695 world_diff.write_storage(&mut NoWorld, &mut (), key.0, key.1, *value, 0);
696 }
697 let actual_changes = world_diff
698 .get_storage_changes_after(&checkpoint1)
699 .collect::<BTreeMap<_, _>>();
700 let expected_changes = first_changes
701 .iter()
702 .map(|(key, value)| {
703 let before = initial_values
704 .get(key)
705 .map_or_else(U256::zero, |slot| slot.value);
706 let is_initial = initial_values
707 .get(key)
708 .is_none_or(|slot| slot.is_write_initial);
709 (
710 *key,
711 StorageChange {
712 before,
713 after: *value,
714 is_initial,
715 },
716 )
717 })
718 .collect();
719 assert_eq!(actual_changes, expected_changes);
720
721 let checkpoint2 = world_diff.snapshot();
722 for (key, value) in &second_changes {
723 world_diff.write_storage(&mut NoWorld, &mut (), key.0, key.1, *value, 0);
724 }
725 let actual_changes = world_diff
726 .get_storage_changes_after(&checkpoint2)
727 .collect::<BTreeMap<_, _>>();
728 let expected_changes = second_changes
729 .iter()
730 .map(|(key, value)| {
731 let before = first_changes
732 .get(key)
733 .or(initial_values.get(key).map(|slot| &slot.value))
734 .copied()
735 .unwrap_or_default();
736 let is_initial = initial_values
737 .get(key)
738 .is_none_or(|slot| slot.is_write_initial);
739 (
740 *key,
741 StorageChange {
742 before,
743 after: *value,
744 is_initial,
745 },
746 )
747 })
748 .collect();
749 assert_eq!(actual_changes, expected_changes);
750
751 let mut combined = first_changes
752 .into_iter()
753 .filter_map(|(key, value)| {
754 let initial = initial_values
755 .get(&key)
756 .copied()
757 .unwrap_or(StorageSlot::EMPTY);
758 (initial.value != value).then_some((
759 key,
760 StorageChange {
761 before: initial.value,
762 after: value,
763 is_initial: initial.is_write_initial,
764 },
765 ))
766 })
767 .collect::<BTreeMap<_, _>>();
768 for (key, value) in second_changes {
769 let initial = initial_values
770 .get(&key)
771 .copied()
772 .unwrap_or(StorageSlot::EMPTY);
773 if initial.value == value {
774 combined.remove(&key);
775 } else {
776 combined.insert(
777 key,
778 StorageChange {
779 before: initial.value,
780 after: value,
781 is_initial: initial.is_write_initial,
782 },
783 );
784 }
785 }
786
787 assert_eq!(combined, world_diff.get_storage_changes().collect());
788 }
789
790 proptest! {
791 #[test]
792 fn storage_changes_work_as_expected(
793 initial_values in arbitrary_initial_storage(),
794 first_changes in arbitrary_storage_changes(),
795 second_changes in arbitrary_storage_changes(),
796 ) {
797 test_storage_changes(&initial_values, first_changes, second_changes);
798 }
799
800 #[test]
801 fn storage_changes_work_with_constrained_changes(
802 initial_values in constrained_initial_storage(),
803 first_changes in constrained_storage_changes(),
804 second_changes in constrained_storage_changes(),
805 ) {
806 test_storage_changes(&initial_values, first_changes, second_changes);
807 }
808 }
809
810 const MAX_ITEMS: usize = 5;
812 const BIT_MASK: u8 = 0b_1111;
814
815 fn arbitrary_initial_storage() -> impl Strategy<Value = BTreeMap<(H160, U256), StorageSlot>> {
816 btree_map(
817 any::<([u8; 20], [u8; 32])>()
818 .prop_map(|(contract, key)| (H160::from(contract), U256::from(key))),
819 any::<([u8; 32], bool)>().prop_map(|(value, is_write_initial)| StorageSlot {
820 value: U256::from(value),
821 is_write_initial,
822 }),
823 0..=MAX_ITEMS,
824 )
825 }
826
827 fn constrained_initial_storage() -> impl Strategy<Value = BTreeMap<(H160, U256), StorageSlot>> {
828 btree_map(
829 (bits::u8::masked(BIT_MASK), bits::u8::masked(BIT_MASK))
830 .prop_map(|(contract, key)| (H160::repeat_byte(contract), U256::from(key))),
831 (bits::u8::masked(BIT_MASK), any::<bool>()).prop_map(|(value, is_write_initial)| {
832 StorageSlot {
833 value: U256::from(value),
834 is_write_initial,
835 }
836 }),
837 0..=MAX_ITEMS,
838 )
839 }
840
841 fn arbitrary_storage_changes() -> impl Strategy<Value = BTreeMap<(H160, U256), U256>> {
842 btree_map(
843 any::<([u8; 20], [u8; 32])>()
844 .prop_map(|(contract, key)| (H160::from(contract), U256::from(key))),
845 any::<[u8; 32]>().prop_map(U256::from),
846 0..=MAX_ITEMS,
847 )
848 }
849
850 fn constrained_storage_changes() -> impl Strategy<Value = BTreeMap<(H160, U256), U256>> {
851 btree_map(
852 (bits::u8::masked(BIT_MASK), bits::u8::masked(BIT_MASK))
853 .prop_map(|(contract, key)| (H160::repeat_byte(contract), U256::from(key))),
854 bits::u8::masked(BIT_MASK).prop_map(U256::from),
855 0..=MAX_ITEMS,
856 )
857 }
858
859 struct NoWorld;
860
861 impl StorageInterface for NoWorld {
862 fn read_storage(&mut self, _: H160, _: U256) -> StorageSlot {
863 StorageSlot::EMPTY
864 }
865
866 fn cost_of_writing_storage(&mut self, _: StorageSlot, _: U256) -> u32 {
867 0
868 }
869
870 fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool {
871 false
872 }
873 }
874
875 struct CostWorld;
879
880 impl StorageInterface for CostWorld {
881 fn read_storage(&mut self, _: H160, _: U256) -> StorageSlot {
882 StorageSlot::EMPTY
883 }
884
885 fn cost_of_writing_storage(&mut self, _: StorageSlot, _: U256) -> u32 {
886 100
887 }
888
889 fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool {
890 false
891 }
892 }
893
894 #[test]
895 fn merged_storage_write_tracks_paid_and_rolls_back() {
896 let mut world_diff = WorldDiff::default();
897 let (contract, key) = (H160::zero(), U256::from(1));
898
899 world_diff.write_storage(&mut CostWorld, &mut (), contract, key, U256::from(5), 0);
901 let e = world_diff.storage_writes.as_ref()[&(contract, key)];
902 assert_eq!((e.value, e.paid), (U256::from(5), 100));
903
904 let snapshot = world_diff.snapshot();
906 world_diff.write_storage(&mut CostWorld, &mut (), contract, key, U256::from(6), 0);
907 let e = world_diff.storage_writes.as_ref()[&(contract, key)];
908 assert_eq!((e.value, e.paid), (U256::from(6), 100));
909
910 world_diff.rollback(snapshot);
912 let e = world_diff.storage_writes.as_ref()[&(contract, key)];
913 assert_eq!((e.value, e.paid), (U256::from(5), 100));
914 }
915
916 #[derive(Default)]
917 struct TestWorld {
918 values: BTreeMap<(H160, U256), U256>,
919 }
920
921 impl StorageInterface for TestWorld {
922 fn read_storage(&mut self, contract: H160, key: U256) -> StorageSlot {
923 let value = self
924 .values
925 .get(&(contract, key))
926 .copied()
927 .unwrap_or_default();
928 StorageSlot {
929 value,
930 is_write_initial: !self.values.contains_key(&(contract, key)),
931 }
932 }
933
934 fn cost_of_writing_storage(&mut self, _: StorageSlot, _: U256) -> u32 {
935 0
936 }
937
938 fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool {
939 false
940 }
941 }
942
943 #[test]
944 fn storage_logs_include_reads_writes_and_rollbacks() {
945 let mut world_diff = WorldDiff::default();
946 let mut world = TestWorld::default();
947 let contract = H160::zero();
948 let key = U256::from(1);
949
950 let (value, _) = world_diff.read_storage(&mut world, &mut (), contract, key, 0);
951 assert_eq!(value, U256::zero());
952
953 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
954 let snapshot = world_diff.snapshot();
955 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
956 world_diff.append_rollback_logs(&snapshot);
957 world_diff.rollback(snapshot);
958
959 let logs = world_diff.storage_log_queries();
960 assert_eq!(logs.len(), 4);
961 assert!(!logs[0].rw_flag);
962 assert!(logs[1].rw_flag && !logs[1].rollback);
963 assert!(logs[2].rw_flag && !logs[2].rollback);
964 assert!(logs[3].rw_flag && logs[3].rollback);
965 assert_eq!(logs[3].read_value, logs[2].read_value);
966 assert_eq!(logs[3].written_value, logs[2].written_value);
967 }
968
969 #[test]
970 fn skip_storage_logs_drops_trace_but_keeps_dedup_inputs() {
971 let mut world_diff = WorldDiff::default();
972 world_diff.set_record_storage_logs(false);
973 let mut world = TestWorld::default();
974 let contract = H160::zero();
975 let key = U256::from(1);
976
977 let (value, _) = world_diff.read_storage(&mut world, &mut (), contract, key, 0);
979 assert_eq!(value, U256::zero());
980 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
981 let snapshot = world_diff.snapshot();
982 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
983 world_diff.append_rollback_logs(&snapshot);
984 world_diff.rollback(snapshot);
985
986 assert!(world_diff.storage_log_queries().is_empty());
988 assert!(world_diff.rollback_storage_logs.is_empty());
989
990 assert!(world_diff
994 .protective_reads_iter()
995 .any(|slot| slot == (contract, key)));
996 assert_eq!(
997 world_diff
998 .initial_storage_value(contract, key)
999 .map(|s| s.value),
1000 Some(U256::zero())
1001 );
1002 }
1003
1004 #[test]
1005 fn recording_mode_does_not_cache_read_only_slots() {
1006 let mut world_diff = WorldDiff::default();
1010 let mut world = TestWorld::default();
1011 let (contract, key) = (H160::zero(), U256::from(1));
1012
1013 let (value, _) = world_diff.read_storage(&mut world, &mut (), contract, key, 0);
1014 assert_eq!(value, U256::zero());
1015
1016 assert_eq!(world_diff.storage_log_queries().len(), 1);
1017 assert!(world_diff.initial_storage_value(contract, key).is_none());
1018 assert!(world_diff.protective_reads_iter().next().is_none());
1019 }
1020
1021 fn dedup_input_set(world_diff: &WorldDiff) -> BTreeSet<(H160, U256)> {
1024 let mut set: BTreeSet<(H160, U256)> = world_diff.protective_reads_iter().collect();
1025 set.extend(world_diff.storage_writes.as_ref().keys().copied());
1026 set
1027 }
1028
1029 #[test]
1030 fn external_rollback_restores_opt_out_dedup_inputs_for_retry() {
1031 let a = (H160::zero(), U256::from(1));
1038 let b = (H160::zero(), U256::from(2));
1039 let c = (H160::zero(), U256::from(3));
1040
1041 let mut world = TestWorld::default();
1042 world.values.insert(b, U256::from(200));
1043 world.values.insert(c, U256::from(300));
1044
1045 let run_kept_attempt = |wd: &mut WorldDiff, world: &mut TestWorld| {
1047 wd.read_storage(world, &mut (), b.0, b.1, 0);
1048 wd.write_storage(world, &mut (), c.0, c.1, U256::from(777), 0);
1049 };
1050
1051 let mut retried = WorldDiff::default();
1054 retried.set_record_storage_logs(false);
1055 let snapshot = retried.external_snapshot();
1056 retried.read_storage(&mut world, &mut (), a.0, a.1, 0);
1057 retried.write_storage(&mut world, &mut (), b.0, b.1, U256::from(999), 0);
1058 retried.external_rollback(snapshot);
1059 run_kept_attempt(&mut retried, &mut world);
1060
1061 let mut clean = WorldDiff::default();
1063 clean.set_record_storage_logs(false);
1064 run_kept_attempt(&mut clean, &mut world);
1065
1066 assert_eq!(dedup_input_set(&retried), dedup_input_set(&clean));
1069 assert_eq!(dedup_input_set(&clean), BTreeSet::from([b, c]));
1070 assert!(!dedup_input_set(&retried).contains(&a));
1071
1072 for slot in dedup_input_set(&clean) {
1074 assert_eq!(
1075 retried.storage_writes.as_ref().get(&slot).map(|e| e.value),
1076 clean.storage_writes.as_ref().get(&slot).map(|e| e.value),
1077 );
1078 assert_eq!(
1079 retried
1080 .initial_storage_value(slot.0, slot.1)
1081 .map(|s| s.value),
1082 clean.initial_storage_value(slot.0, slot.1).map(|s| s.value),
1083 );
1084 }
1085 }
1086
1087 #[test]
1088 fn protective_read_survives_internal_rollback_but_not_external() {
1089 let a = (H160::zero(), U256::from(1));
1093 let mut world = TestWorld::default();
1094 world.values.insert(a, U256::from(100));
1095
1096 let mut wd = WorldDiff::default();
1097 wd.set_record_storage_logs(false);
1098
1099 let external = wd.external_snapshot();
1100 wd.read_storage(&mut world, &mut (), a.0, a.1, 0); let internal = wd.snapshot();
1104 wd.write_storage(&mut world, &mut (), a.0, a.1, U256::from(999), 0);
1105 wd.rollback(internal);
1106 assert!(
1107 wd.protective_reads_iter().any(|s| s == a),
1108 "protective read must survive an internal frame rollback",
1109 );
1110
1111 wd.external_rollback(external);
1113 assert!(
1114 !wd.protective_reads_iter().any(|s| s == a),
1115 "external rollback must clear the protective read",
1116 );
1117 }
1118
1119 #[test]
1120 fn protective_read_not_set_when_write_is_pending() {
1121 let a = (H160::zero(), U256::from(1));
1126 let mut world = TestWorld::default();
1127 world.values.insert(a, U256::from(100));
1128
1129 let mut wd = WorldDiff::default();
1130 wd.set_record_storage_logs(false);
1131
1132 wd.write_storage(&mut world, &mut (), a.0, a.1, U256::from(5), 0);
1133 let (value, _) = wd.read_storage(&mut world, &mut (), a.0, a.1, 0);
1134
1135 assert_eq!(value, U256::from(5), "read must observe the pending write");
1136 assert!(
1137 !wd.protective_reads_iter().any(|s| s == a),
1138 "a read with a pending write must not be a protective read",
1139 );
1140 assert!(
1141 dedup_input_set(&wd).contains(&a),
1142 "slot is still in the dedup set as a write"
1143 );
1144 }
1145
1146 #[test]
1147 #[should_panic(expected = "before any storage access")]
1148 fn set_record_storage_logs_after_access_panics() {
1149 let mut world_diff = WorldDiff::default();
1152 let mut world = TestWorld::default();
1153 world_diff.read_storage(&mut world, &mut (), H160::zero(), U256::from(1), 0);
1154 world_diff.set_record_storage_logs(false);
1155 }
1156
1157 #[test]
1158 fn rollback_without_append_keeps_storage_log_stream_unchanged() {
1159 let mut world_diff = WorldDiff::default();
1160 let mut world = TestWorld::default();
1161 let contract = H160::zero();
1162 let key = U256::from(1);
1163
1164 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
1165 let snapshot = world_diff.snapshot();
1166 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
1167 world_diff.rollback(snapshot);
1168
1169 let logs = world_diff.storage_log_queries();
1170 assert_eq!(logs.len(), 2);
1171 assert!(logs.iter().all(|log| log.rw_flag && !log.rollback));
1172 assert_eq!(world_diff.rollback_storage_logs.len(), 2);
1173 }
1174
1175 #[test]
1176 fn external_rollback_truncates_storage_logs_to_internal_snapshot() {
1177 let mut world_diff = WorldDiff::default();
1178 let mut world = TestWorld::default();
1179 let contract = H160::zero();
1180 let key = U256::from(1);
1181
1182 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
1183 let snapshot = world_diff.external_snapshot();
1184 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
1185
1186 world_diff.external_rollback(snapshot);
1187
1188 let logs = world_diff.storage_log_queries();
1189 assert_eq!(logs.len(), 1);
1190 assert!(logs[0].rw_flag && !logs[0].rollback);
1191 assert_eq!(world_diff.rollback_storage_logs.len(), 1);
1192 }
1193
1194 #[test]
1195 fn storage_read_log_sets_written_value_to_read_value() {
1196 let mut world_diff = WorldDiff::default();
1197 let mut world = TestWorld::default();
1198 let contract = H160::repeat_byte(1);
1199 let key = U256::from(7);
1200 let value = U256::from(33);
1201 world.values.insert((contract, key), value);
1202
1203 let (read_value, _) = world_diff.read_storage(&mut world, &mut (), contract, key, 0);
1204 assert_eq!(read_value, value);
1205
1206 let logs = world_diff.storage_log_queries();
1207 assert_eq!(logs.len(), 1);
1208 assert!(!logs[0].rw_flag);
1209 assert_eq!(logs[0].read_value, value);
1210 assert_eq!(logs[0].written_value, value);
1211 }
1212}