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, Default)]
19pub struct WorldDiff {
20 storage_changes: RollbackableMap<(H160, U256), U256>,
22 paid_changes: RollbackableMap<(H160, U256), u32>,
23 transient_storage_changes: RollbackableMap<(H160, U256), U256>,
24 events: RollbackableLog<Event>,
25 l2_to_l1_logs: RollbackableLog<L2ToL1Log>,
26 pub(crate) pubdata: RollbackablePod<i32>,
27 storage_refunds: RollbackableLog<u32>,
28 pubdata_costs: RollbackableLog<i32>,
29 storage_logs: Vec<LogQuery>,
30 rollback_storage_logs: Vec<LogQuery>,
31
32 pub(crate) decommitted_hashes: RollbackableMap<U256, DecommitState>,
41 decommit_pinned_pages: RollbackableSet<u32>,
48 read_storage_slots: RollbackableSet<(H160, U256)>,
49 written_storage_slots: RollbackableSet<(H160, U256)>,
50
51 storage_initial_values: BTreeMap<(H160, U256), StorageSlot>,
53}
54
55#[derive(Debug)]
56pub(crate) struct ExternalSnapshot {
57 internal_snapshot: Snapshot,
58 pub(crate) decommitted_hashes: <RollbackableMap<U256, DecommitState> as Rollback>::Snapshot,
59 decommit_pinned_pages: <RollbackableSet<u32> as Rollback>::Snapshot,
60 read_storage_slots: <RollbackableMap<(H160, U256), ()> as Rollback>::Snapshot,
61 written_storage_slots: <RollbackableMap<(H160, U256), ()> as Rollback>::Snapshot,
62 storage_refunds: <RollbackableLog<u32> as Rollback>::Snapshot,
63 pubdata_costs: <RollbackableLog<i32> as Rollback>::Snapshot,
64}
65
66#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
67pub(crate) enum DecommitState {
68 #[default]
77 Unsuccessful,
78 Succeeded(u32),
80}
81
82impl WorldDiff {
83 pub(crate) fn read_storage(
85 &mut self,
86 world: &mut impl StorageInterface,
87 tracer: &mut impl Tracer,
88 contract: H160,
89 key: U256,
90 tx_number_in_block: u16,
91 ) -> (U256, u32) {
92 let (value, newly_added) =
93 self.read_storage_inner(world, tracer, contract, key, tx_number_in_block);
94 let refund = if !newly_added || world.is_free_storage_slot(&contract, &key) {
95 WARM_READ_REFUND
96 } else {
97 0
98 };
99 self.storage_refunds.push(refund);
100 (value, refund)
101 }
102
103 pub(crate) fn read_storage_without_refund(
107 &mut self,
108 world: &mut impl StorageInterface,
109 tracer: &mut impl Tracer,
110 contract: H160,
111 key: U256,
112 tx_number_in_block: u16,
113 ) -> U256 {
114 self.read_storage_inner(world, tracer, contract, key, tx_number_in_block)
115 .0
116 }
117
118 fn read_storage_inner(
119 &mut self,
120 world: &mut impl StorageInterface,
121 tracer: &mut impl Tracer,
122 contract: H160,
123 key: U256,
124 tx_number_in_block: u16,
125 ) -> (U256, bool) {
126 let newly_added = self.read_storage_slots.add((contract, key));
127 if newly_added {
128 tracer.on_extra_prover_cycles(CycleStats::StorageRead);
129 }
130
131 self.pubdata_costs.push(0);
132 let value = self.just_read_storage(world, contract, key);
133 self.storage_logs.push(LogQuery {
136 timestamp: Timestamp(
137 u32::try_from(self.storage_logs.len()).expect("Too many storage logs"),
138 ),
139 tx_number_in_block,
140 aux_byte: STORAGE_AUX_BYTE,
141 shard_id: 0,
142 address: contract,
143 key,
144 read_value: value,
145 written_value: value,
146 rw_flag: false,
147 rollback: false,
148 is_service: false,
149 });
150 (value, newly_added)
151 }
152
153 pub(crate) fn just_read_storage(
156 &self,
157 world: &mut impl StorageInterface,
158 contract: H160,
159 key: U256,
160 ) -> U256 {
161 self.storage_changes
162 .as_ref()
163 .get(&(contract, key))
164 .copied()
165 .unwrap_or_else(|| world.read_storage_value(contract, key))
166 }
167
168 pub(crate) fn write_storage(
170 &mut self,
171 world: &mut impl StorageInterface,
172 tracer: &mut impl Tracer,
173 contract: H160,
174 key: U256,
175 value: U256,
176 tx_number_in_block: u16,
177 ) -> u32 {
178 let read_value = self.just_read_storage(world, contract, key);
179 let log_query = LogQuery {
180 timestamp: Timestamp(u32::try_from(self.storage_logs.len()).unwrap_or(u32::MAX)),
181 tx_number_in_block,
182 aux_byte: STORAGE_AUX_BYTE,
183 shard_id: 0,
184 address: contract,
185 key,
186 read_value,
187 written_value: value,
188 rw_flag: true,
189 rollback: false,
190 is_service: false,
191 };
192 self.storage_logs.push(log_query);
193 self.rollback_storage_logs.push(LogQuery {
194 rollback: true,
195 ..log_query
196 });
197
198 self.storage_changes.insert((contract, key), value);
199
200 let initial_value = self
201 .storage_initial_values
202 .entry((contract, key))
203 .or_insert_with(|| world.read_storage(contract, key));
204
205 if world.is_free_storage_slot(&contract, &key) {
206 if self.written_storage_slots.add((contract, key)) {
207 tracer.on_extra_prover_cycles(CycleStats::StorageWrite);
208 }
209 self.read_storage_slots.add((contract, key));
210
211 self.storage_refunds.push(WARM_WRITE_REFUND);
212 self.pubdata_costs.push(0);
213 return WARM_WRITE_REFUND;
214 }
215
216 let update_cost = world.cost_of_writing_storage(*initial_value, value);
217 let prepaid = self
218 .paid_changes
219 .insert((contract, key), update_cost)
220 .unwrap_or(0);
221
222 let refund = if self.written_storage_slots.add((contract, key)) {
223 tracer.on_extra_prover_cycles(CycleStats::StorageWrite);
224
225 if self.read_storage_slots.add((contract, key)) {
226 0
227 } else {
228 COLD_WRITE_AFTER_WARM_READ_REFUND
229 }
230 } else {
231 WARM_WRITE_REFUND
232 };
233
234 #[allow(clippy::cast_possible_wrap)]
235 {
236 let pubdata_cost = (update_cost as i32) - (prepaid as i32);
237 self.pubdata.0 += pubdata_cost;
238 self.storage_refunds.push(refund);
239 self.pubdata_costs.push(pubdata_cost);
240 }
241 refund
242 }
243
244 pub(crate) fn pubdata(&self) -> i32 {
245 self.pubdata.0
246 }
247
248 pub fn storage_refunds(&self) -> &[u32] {
250 self.storage_refunds.as_ref()
251 }
252
253 pub fn pubdata_costs(&self) -> &[i32] {
255 self.pubdata_costs.as_ref()
256 }
257
258 pub fn storage_log_queries(&self) -> &[LogQuery] {
265 &self.storage_logs
266 }
267
268 pub fn storage_log_queries_after(&self, snapshot: &Snapshot) -> &[LogQuery] {
270 &self.storage_logs[snapshot.storage_logs_len..]
271 }
272
273 #[doc(hidden)] pub fn get_storage_state(&self) -> &BTreeMap<(H160, U256), U256> {
275 self.storage_changes.as_ref()
276 }
277
278 pub fn get_storage_changes(&self) -> impl Iterator<Item = ((H160, U256), StorageChange)> + '_ {
280 self.storage_changes
281 .as_ref()
282 .iter()
283 .filter_map(|(key, &value)| {
284 let initial_slot = &self.storage_initial_values[key];
285 if initial_slot.value == value {
286 None
287 } else {
288 Some((
289 *key,
290 StorageChange {
291 before: initial_slot.value,
292 after: value,
293 is_initial: initial_slot.is_write_initial,
294 },
295 ))
296 }
297 })
298 }
299
300 pub fn get_storage_changes_after(
302 &self,
303 snapshot: &Snapshot,
304 ) -> impl Iterator<Item = ((H160, U256), StorageChange)> + '_ {
305 self.storage_changes
306 .changes_after(snapshot.storage_changes)
307 .into_iter()
308 .map(|(key, (before, after))| {
309 let initial = self.storage_initial_values[&key];
310 (
311 key,
312 StorageChange {
313 before: before.unwrap_or(initial.value),
314 after,
315 is_initial: initial.is_write_initial,
316 },
317 )
318 })
319 }
320
321 pub(crate) fn read_transient_storage(&mut self, contract: H160, key: U256) -> U256 {
322 self.pubdata_costs.push(0);
323 self.transient_storage_changes
324 .as_ref()
325 .get(&(contract, key))
326 .copied()
327 .unwrap_or_default()
328 }
329
330 pub(crate) fn write_transient_storage(&mut self, contract: H160, key: U256, value: U256) {
331 self.pubdata_costs.push(0);
332 self.transient_storage_changes
333 .insert((contract, key), value);
334 }
335
336 pub(crate) fn get_transient_storage_state(&self) -> &BTreeMap<(H160, U256), U256> {
337 self.transient_storage_changes.as_ref()
338 }
339
340 pub(crate) fn record_event(&mut self, event: Event) {
341 self.events.push(event);
342 }
343
344 pub(crate) fn events(&self) -> &[Event] {
345 self.events.as_ref()
346 }
347
348 pub fn events_after(&self, snapshot: &Snapshot) -> &[Event] {
350 self.events.logs_after(snapshot.events)
351 }
352
353 pub(crate) fn record_l2_to_l1_log(&mut self, log: L2ToL1Log) {
354 self.l2_to_l1_logs.push(log);
355 }
356
357 pub(crate) fn l2_to_l1_logs(&self) -> &[L2ToL1Log] {
358 self.l2_to_l1_logs.as_ref()
359 }
360
361 pub fn l2_to_l1_logs_after(&self, snapshot: &Snapshot) -> &[L2ToL1Log] {
363 self.l2_to_l1_logs.logs_after(snapshot.l2_to_l1_logs)
364 }
365
366 pub fn decommitted_hashes(&self) -> impl Iterator<Item = U256> + '_ {
372 self.decommitted_hashes.as_ref().keys().copied()
373 }
374
375 pub(crate) fn decommit_page(&self, code_hash: U256) -> Option<HeapId> {
376 self.decommitted_hashes
377 .as_ref()
378 .get(&code_hash)
379 .and_then(|state| {
380 if let DecommitState::Succeeded(page) = state {
381 Some(HeapId::from_u32_unchecked(*page))
382 } else {
383 None
384 }
385 })
386 }
387
388 pub(crate) fn is_decommit_page_pinned(&self, page: HeapId) -> bool {
389 self.decommit_pinned_pages.as_ref().contains(&page.as_u32())
390 }
391
392 pub(crate) fn set_decommit_page(&mut self, code_hash: U256, page: HeapId) {
393 self.decommitted_hashes
394 .insert(code_hash, DecommitState::Succeeded(page.as_u32()));
395 self.decommit_pinned_pages.add(page.as_u32());
396 }
397
398 pub fn snapshot(&self) -> Snapshot {
400 Snapshot {
401 storage_changes: self.storage_changes.snapshot(),
402 paid_changes: self.paid_changes.snapshot(),
403 events: self.events.snapshot(),
404 l2_to_l1_logs: self.l2_to_l1_logs.snapshot(),
405 transient_storage_changes: self.transient_storage_changes.snapshot(),
406 pubdata: self.pubdata.snapshot(),
407 storage_logs_len: self.storage_logs.len(),
408 rollback_storage_logs_len: self.rollback_storage_logs.len(),
409 }
410 }
411
412 pub(crate) fn append_rollback_logs(&mut self, snapshot: &Snapshot) {
417 if self.rollback_storage_logs.len() > snapshot.rollback_storage_logs_len {
418 let rollback_logs = self
419 .rollback_storage_logs
420 .split_off(snapshot.rollback_storage_logs_len);
421 for log in rollback_logs.into_iter().rev() {
422 self.storage_logs.push(log);
423 }
424 }
425 }
426
427 #[allow(clippy::needless_pass_by_value)] pub(crate) fn rollback(&mut self, snapshot: Snapshot) {
429 self.storage_changes.rollback(snapshot.storage_changes);
430 self.paid_changes.rollback(snapshot.paid_changes);
431 self.events.rollback(snapshot.events);
432 self.l2_to_l1_logs.rollback(snapshot.l2_to_l1_logs);
433 self.transient_storage_changes
434 .rollback(snapshot.transient_storage_changes);
435 self.pubdata.rollback(snapshot.pubdata);
436 }
437
438 pub(crate) fn external_snapshot(&self) -> ExternalSnapshot {
441 ExternalSnapshot {
446 internal_snapshot: Snapshot {
447 transient_storage_changes: 0,
448 ..self.snapshot()
449 },
450 decommitted_hashes: self.decommitted_hashes.snapshot(),
451 decommit_pinned_pages: self.decommit_pinned_pages.snapshot(),
452 read_storage_slots: self.read_storage_slots.snapshot(),
453 written_storage_slots: self.written_storage_slots.snapshot(),
454 storage_refunds: self.storage_refunds.snapshot(),
455 pubdata_costs: self.pubdata_costs.snapshot(),
456 }
457 }
458
459 pub(crate) fn external_rollback(&mut self, snapshot: ExternalSnapshot) {
460 let storage_logs_len = snapshot.internal_snapshot.storage_logs_len;
461 let rollback_storage_logs_len = snapshot.internal_snapshot.rollback_storage_logs_len;
462
463 self.rollback(snapshot.internal_snapshot);
464 self.storage_refunds.rollback(snapshot.storage_refunds);
465 self.pubdata_costs.rollback(snapshot.pubdata_costs);
466 self.decommitted_hashes
467 .rollback(snapshot.decommitted_hashes);
468 self.decommit_pinned_pages
469 .rollback(snapshot.decommit_pinned_pages);
470 self.read_storage_slots
471 .rollback(snapshot.read_storage_slots);
472 self.written_storage_slots
473 .rollback(snapshot.written_storage_slots);
474 self.storage_logs.truncate(storage_logs_len);
475 self.rollback_storage_logs
476 .truncate(rollback_storage_logs_len);
477 }
478
479 pub(crate) fn delete_history(&mut self) {
480 self.storage_changes.delete_history();
481 self.paid_changes.delete_history();
482 self.transient_storage_changes.delete_history();
483 self.events.delete_history();
484 self.l2_to_l1_logs.delete_history();
485 self.pubdata.delete_history();
486 self.storage_refunds.delete_history();
487 self.pubdata_costs.delete_history();
488 self.decommitted_hashes.delete_history();
489 self.decommit_pinned_pages.delete_history();
490 self.read_storage_slots.delete_history();
491 self.written_storage_slots.delete_history();
492 }
493
494 pub(crate) fn clear_transient_storage(&mut self) {
495 self.transient_storage_changes = RollbackableMap::default();
496 }
497}
498
499#[derive(Clone, PartialEq, Debug)]
502pub struct Snapshot {
503 storage_changes: <RollbackableMap<(H160, U256), U256> as Rollback>::Snapshot,
504 paid_changes: <RollbackableMap<(H160, U256), u32> as Rollback>::Snapshot,
505 events: <RollbackableLog<Event> as Rollback>::Snapshot,
506 l2_to_l1_logs: <RollbackableLog<L2ToL1Log> as Rollback>::Snapshot,
507 transient_storage_changes: <RollbackableMap<(H160, U256), U256> as Rollback>::Snapshot,
508 pubdata: <RollbackablePod<i32> as Rollback>::Snapshot,
509 storage_logs_len: usize,
510 rollback_storage_logs_len: usize,
511}
512
513#[derive(Debug, PartialEq)]
515pub struct StorageChange {
516 pub before: U256,
518 pub after: U256,
520 pub is_initial: bool,
523}
524
525const WARM_READ_REFUND: u32 = STORAGE_ACCESS_COLD_READ_COST - STORAGE_ACCESS_WARM_READ_COST;
526const WARM_WRITE_REFUND: u32 = STORAGE_ACCESS_COLD_WRITE_COST - STORAGE_ACCESS_WARM_WRITE_COST;
527const COLD_WRITE_AFTER_WARM_READ_REFUND: u32 = STORAGE_ACCESS_COLD_READ_COST;
528
529#[cfg(test)]
530mod tests {
531 use proptest::{bits, collection::btree_map, prelude::*};
532
533 use super::*;
534 use crate::StorageSlot;
535
536 fn test_storage_changes(
537 initial_values: &BTreeMap<(H160, U256), StorageSlot>,
538 first_changes: BTreeMap<(H160, U256), U256>,
539 second_changes: BTreeMap<(H160, U256), U256>,
540 ) {
541 let mut world_diff = WorldDiff {
542 storage_initial_values: initial_values.clone(),
543 ..WorldDiff::default()
544 };
545
546 let checkpoint1 = world_diff.snapshot();
547 for (key, value) in &first_changes {
548 world_diff.write_storage(&mut NoWorld, &mut (), key.0, key.1, *value, 0);
549 }
550 let actual_changes = world_diff
551 .get_storage_changes_after(&checkpoint1)
552 .collect::<BTreeMap<_, _>>();
553 let expected_changes = first_changes
554 .iter()
555 .map(|(key, value)| {
556 let before = initial_values
557 .get(key)
558 .map_or_else(U256::zero, |slot| slot.value);
559 let is_initial = initial_values
560 .get(key)
561 .is_none_or(|slot| slot.is_write_initial);
562 (
563 *key,
564 StorageChange {
565 before,
566 after: *value,
567 is_initial,
568 },
569 )
570 })
571 .collect();
572 assert_eq!(actual_changes, expected_changes);
573
574 let checkpoint2 = world_diff.snapshot();
575 for (key, value) in &second_changes {
576 world_diff.write_storage(&mut NoWorld, &mut (), key.0, key.1, *value, 0);
577 }
578 let actual_changes = world_diff
579 .get_storage_changes_after(&checkpoint2)
580 .collect::<BTreeMap<_, _>>();
581 let expected_changes = second_changes
582 .iter()
583 .map(|(key, value)| {
584 let before = first_changes
585 .get(key)
586 .or(initial_values.get(key).map(|slot| &slot.value))
587 .copied()
588 .unwrap_or_default();
589 let is_initial = initial_values
590 .get(key)
591 .is_none_or(|slot| slot.is_write_initial);
592 (
593 *key,
594 StorageChange {
595 before,
596 after: *value,
597 is_initial,
598 },
599 )
600 })
601 .collect();
602 assert_eq!(actual_changes, expected_changes);
603
604 let mut combined = first_changes
605 .into_iter()
606 .filter_map(|(key, value)| {
607 let initial = initial_values
608 .get(&key)
609 .copied()
610 .unwrap_or(StorageSlot::EMPTY);
611 (initial.value != value).then_some((
612 key,
613 StorageChange {
614 before: initial.value,
615 after: value,
616 is_initial: initial.is_write_initial,
617 },
618 ))
619 })
620 .collect::<BTreeMap<_, _>>();
621 for (key, value) in second_changes {
622 let initial = initial_values
623 .get(&key)
624 .copied()
625 .unwrap_or(StorageSlot::EMPTY);
626 if initial.value == value {
627 combined.remove(&key);
628 } else {
629 combined.insert(
630 key,
631 StorageChange {
632 before: initial.value,
633 after: value,
634 is_initial: initial.is_write_initial,
635 },
636 );
637 }
638 }
639
640 assert_eq!(combined, world_diff.get_storage_changes().collect());
641 }
642
643 proptest! {
644 #[test]
645 fn storage_changes_work_as_expected(
646 initial_values in arbitrary_initial_storage(),
647 first_changes in arbitrary_storage_changes(),
648 second_changes in arbitrary_storage_changes(),
649 ) {
650 test_storage_changes(&initial_values, first_changes, second_changes);
651 }
652
653 #[test]
654 fn storage_changes_work_with_constrained_changes(
655 initial_values in constrained_initial_storage(),
656 first_changes in constrained_storage_changes(),
657 second_changes in constrained_storage_changes(),
658 ) {
659 test_storage_changes(&initial_values, first_changes, second_changes);
660 }
661 }
662
663 const MAX_ITEMS: usize = 5;
665 const BIT_MASK: u8 = 0b_1111;
667
668 fn arbitrary_initial_storage() -> impl Strategy<Value = BTreeMap<(H160, U256), StorageSlot>> {
669 btree_map(
670 any::<([u8; 20], [u8; 32])>()
671 .prop_map(|(contract, key)| (H160::from(contract), U256::from(key))),
672 any::<([u8; 32], bool)>().prop_map(|(value, is_write_initial)| StorageSlot {
673 value: U256::from(value),
674 is_write_initial,
675 }),
676 0..=MAX_ITEMS,
677 )
678 }
679
680 fn constrained_initial_storage() -> impl Strategy<Value = BTreeMap<(H160, U256), StorageSlot>> {
681 btree_map(
682 (bits::u8::masked(BIT_MASK), bits::u8::masked(BIT_MASK))
683 .prop_map(|(contract, key)| (H160::repeat_byte(contract), U256::from(key))),
684 (bits::u8::masked(BIT_MASK), any::<bool>()).prop_map(|(value, is_write_initial)| {
685 StorageSlot {
686 value: U256::from(value),
687 is_write_initial,
688 }
689 }),
690 0..=MAX_ITEMS,
691 )
692 }
693
694 fn arbitrary_storage_changes() -> impl Strategy<Value = BTreeMap<(H160, U256), U256>> {
695 btree_map(
696 any::<([u8; 20], [u8; 32])>()
697 .prop_map(|(contract, key)| (H160::from(contract), U256::from(key))),
698 any::<[u8; 32]>().prop_map(U256::from),
699 0..=MAX_ITEMS,
700 )
701 }
702
703 fn constrained_storage_changes() -> impl Strategy<Value = BTreeMap<(H160, U256), U256>> {
704 btree_map(
705 (bits::u8::masked(BIT_MASK), bits::u8::masked(BIT_MASK))
706 .prop_map(|(contract, key)| (H160::repeat_byte(contract), U256::from(key))),
707 bits::u8::masked(BIT_MASK).prop_map(U256::from),
708 0..=MAX_ITEMS,
709 )
710 }
711
712 struct NoWorld;
713
714 impl StorageInterface for NoWorld {
715 fn read_storage(&mut self, _: H160, _: U256) -> StorageSlot {
716 StorageSlot::EMPTY
717 }
718
719 fn cost_of_writing_storage(&mut self, _: StorageSlot, _: U256) -> u32 {
720 0
721 }
722
723 fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool {
724 false
725 }
726 }
727
728 #[derive(Default)]
729 struct TestWorld {
730 values: BTreeMap<(H160, U256), U256>,
731 }
732
733 impl StorageInterface for TestWorld {
734 fn read_storage(&mut self, contract: H160, key: U256) -> StorageSlot {
735 let value = self
736 .values
737 .get(&(contract, key))
738 .copied()
739 .unwrap_or_default();
740 StorageSlot {
741 value,
742 is_write_initial: !self.values.contains_key(&(contract, key)),
743 }
744 }
745
746 fn cost_of_writing_storage(&mut self, _: StorageSlot, _: U256) -> u32 {
747 0
748 }
749
750 fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool {
751 false
752 }
753 }
754
755 #[test]
756 fn storage_logs_include_reads_writes_and_rollbacks() {
757 let mut world_diff = WorldDiff::default();
758 let mut world = TestWorld::default();
759 let contract = H160::zero();
760 let key = U256::from(1);
761
762 let (value, _) = world_diff.read_storage(&mut world, &mut (), contract, key, 0);
763 assert_eq!(value, U256::zero());
764
765 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
766 let snapshot = world_diff.snapshot();
767 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
768 world_diff.append_rollback_logs(&snapshot);
769 world_diff.rollback(snapshot);
770
771 let logs = world_diff.storage_log_queries();
772 assert_eq!(logs.len(), 4);
773 assert!(!logs[0].rw_flag);
774 assert!(logs[1].rw_flag && !logs[1].rollback);
775 assert!(logs[2].rw_flag && !logs[2].rollback);
776 assert!(logs[3].rw_flag && logs[3].rollback);
777 assert_eq!(logs[3].read_value, logs[2].read_value);
778 assert_eq!(logs[3].written_value, logs[2].written_value);
779 }
780
781 #[test]
782 fn rollback_without_append_keeps_storage_log_stream_unchanged() {
783 let mut world_diff = WorldDiff::default();
784 let mut world = TestWorld::default();
785 let contract = H160::zero();
786 let key = U256::from(1);
787
788 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
789 let snapshot = world_diff.snapshot();
790 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
791 world_diff.rollback(snapshot);
792
793 let logs = world_diff.storage_log_queries();
794 assert_eq!(logs.len(), 2);
795 assert!(logs.iter().all(|log| log.rw_flag && !log.rollback));
796 assert_eq!(world_diff.rollback_storage_logs.len(), 2);
797 }
798
799 #[test]
800 fn external_rollback_truncates_storage_logs_to_internal_snapshot() {
801 let mut world_diff = WorldDiff::default();
802 let mut world = TestWorld::default();
803 let contract = H160::zero();
804 let key = U256::from(1);
805
806 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(10), 0);
807 let snapshot = world_diff.external_snapshot();
808 world_diff.write_storage(&mut world, &mut (), contract, key, U256::from(20), 0);
809
810 world_diff.external_rollback(snapshot);
811
812 let logs = world_diff.storage_log_queries();
813 assert_eq!(logs.len(), 1);
814 assert!(logs[0].rw_flag && !logs[0].rollback);
815 assert_eq!(world_diff.rollback_storage_logs.len(), 1);
816 }
817
818 #[test]
819 fn storage_read_log_sets_written_value_to_read_value() {
820 let mut world_diff = WorldDiff::default();
821 let mut world = TestWorld::default();
822 let contract = H160::repeat_byte(1);
823 let key = U256::from(7);
824 let value = U256::from(33);
825 world.values.insert((contract, key), value);
826
827 let (read_value, _) = world_diff.read_storage(&mut world, &mut (), contract, key, 0);
828 assert_eq!(read_value, value);
829
830 let logs = world_diff.storage_log_queries();
831 assert_eq!(logs.len(), 1);
832 assert!(!logs[0].rw_flag);
833 assert_eq!(logs[0].read_value, value);
834 assert_eq!(logs[0].written_value, value);
835 }
836}