1use std::collections::{hash_map, HashMap, HashSet};
2use std::slice;
3
4use fake::Dummy;
5
6use crate::{
7 BlockHash,
8 CasmHash,
9 ClassHash,
10 ContractAddress,
11 ContractNonce,
12 SierraHash,
13 StateCommitment,
14 StateDiffCommitment,
15 StorageAddress,
16 StorageValue,
17};
18
19#[derive(Default, Debug, Clone, PartialEq)]
20pub struct StateUpdate {
21 pub block_hash: BlockHash,
22 pub parent_state_commitment: StateCommitment,
23 pub state_commitment: StateCommitment,
24 pub contract_updates: HashMap<ContractAddress, ContractUpdate>,
25 pub system_contract_updates: HashMap<ContractAddress, SystemContractUpdate>,
26 pub declared_cairo_classes: HashSet<ClassHash>,
27 pub declared_sierra_classes: HashMap<SierraHash, CasmHash>,
28 pub migrated_compiled_classes: HashMap<SierraHash, CasmHash>,
29}
30
31#[derive(Default, Debug, Clone, PartialEq, Dummy)]
32pub struct StateUpdateData {
33 pub contract_updates: HashMap<ContractAddress, ContractUpdate>,
34 pub system_contract_updates: HashMap<ContractAddress, SystemContractUpdate>,
35 pub declared_cairo_classes: HashSet<ClassHash>,
36 pub declared_sierra_classes: HashMap<SierraHash, CasmHash>,
37 pub migrated_compiled_classes: HashMap<SierraHash, CasmHash>,
38}
39
40#[derive(Default, Debug, Clone, PartialEq, Dummy)]
41pub struct ContractUpdate {
42 pub storage: HashMap<StorageAddress, StorageValue>,
43 pub class: Option<ContractClassUpdate>,
46 pub nonce: Option<ContractNonce>,
47}
48
49#[derive(Default, Debug, Clone, PartialEq, Dummy)]
50pub struct SystemContractUpdate {
51 pub storage: HashMap<StorageAddress, StorageValue>,
52}
53
54#[derive(Debug, Copy, Clone, PartialEq, Dummy)]
55pub enum ContractClassUpdate {
56 Deploy(ClassHash),
57 Replace(ClassHash),
58}
59
60pub struct StateUpdateRef<'a> {
61 pub contract_updates: Vec<(&'a ContractAddress, ContractUpdateRef<'a>)>,
62 pub system_contract_updates: Vec<(&'a ContractAddress, SystemContractUpdateRef<'a>)>,
63 pub declared_sierra_classes: &'a HashMap<SierraHash, CasmHash>,
64 pub migrated_compiled_classes: &'a HashMap<SierraHash, CasmHash>,
65}
66
67pub struct ContractUpdateRef<'a> {
68 pub storage: StorageRef<'a>,
69 pub class: &'a Option<ContractClassUpdate>,
70 pub nonce: &'a Option<ContractNonce>,
71}
72
73pub struct SystemContractUpdateRef<'a> {
74 pub storage: StorageRef<'a>,
75}
76
77#[derive(Copy, Clone)]
78pub enum StorageRef<'a> {
79 HashMap(&'a HashMap<StorageAddress, StorageValue>),
80 Vec(&'a Vec<(StorageAddress, StorageValue)>),
81}
82
83pub enum StorageRefIter<'a> {
84 HashMap(hash_map::Iter<'a, StorageAddress, StorageValue>),
85 Vec(slice::Iter<'a, (StorageAddress, StorageValue)>),
86}
87
88impl ContractUpdate {
89 pub fn replaced_class(&self) -> Option<&ClassHash> {
90 match &self.class {
91 Some(ContractClassUpdate::Replace(hash)) => Some(hash),
92 _ => None,
93 }
94 }
95
96 pub fn deployed_class(&self) -> Option<&ClassHash> {
97 match &self.class {
98 Some(ContractClassUpdate::Deploy(hash)) => Some(hash),
99 _ => None,
100 }
101 }
102}
103
104impl ContractClassUpdate {
105 pub fn class_hash(&self) -> ClassHash {
106 match self {
107 ContractClassUpdate::Deploy(x) => *x,
108 ContractClassUpdate::Replace(x) => *x,
109 }
110 }
111
112 pub fn is_replaced(&self) -> bool {
113 matches!(self, ContractClassUpdate::Replace(_))
114 }
115}
116
117impl StateUpdate {
118 pub fn with_block_hash(mut self, block_hash: BlockHash) -> Self {
119 self.block_hash = block_hash;
120 self
121 }
122
123 pub fn with_state_commitment(mut self, state_commitment: StateCommitment) -> Self {
124 self.state_commitment = state_commitment;
125 self
126 }
127
128 pub fn with_parent_state_commitment(
129 mut self,
130 parent_state_commitment: StateCommitment,
131 ) -> Self {
132 self.parent_state_commitment = parent_state_commitment;
133 self
134 }
135
136 pub fn with_contract_nonce(mut self, contract: ContractAddress, nonce: ContractNonce) -> Self {
137 self.contract_updates.entry(contract).or_default().nonce = Some(nonce);
138 self
139 }
140
141 pub fn with_storage_update(
142 mut self,
143 contract: ContractAddress,
144 key: StorageAddress,
145 value: StorageValue,
146 ) -> Self {
147 self.contract_updates
148 .entry(contract)
149 .or_default()
150 .storage
151 .insert(key, value);
152 self
153 }
154
155 pub fn with_system_storage_update(
156 mut self,
157 contract: ContractAddress,
158 key: StorageAddress,
159 value: StorageValue,
160 ) -> Self {
161 self.system_contract_updates
162 .entry(contract)
163 .or_default()
164 .storage
165 .insert(key, value);
166 self
167 }
168
169 pub fn with_deployed_contract(mut self, contract: ContractAddress, class: ClassHash) -> Self {
170 self.contract_updates.entry(contract).or_default().class =
171 Some(ContractClassUpdate::Deploy(class));
172 self
173 }
174
175 pub fn with_replaced_class(mut self, contract: ContractAddress, class: ClassHash) -> Self {
176 self.contract_updates.entry(contract).or_default().class =
177 Some(ContractClassUpdate::Replace(class));
178 self
179 }
180
181 pub fn with_declared_sierra_class(mut self, sierra: SierraHash, casm: CasmHash) -> Self {
182 self.declared_sierra_classes.insert(sierra, casm);
183 self
184 }
185
186 pub fn with_declared_cairo_class(mut self, cairo: ClassHash) -> Self {
187 self.declared_cairo_classes.insert(cairo);
188 self
189 }
190
191 pub fn with_migrated_compiled_class(mut self, sierra: SierraHash, casm: CasmHash) -> Self {
192 self.migrated_compiled_classes.insert(sierra, casm);
193 self
194 }
195
196 pub fn change_count(&self) -> usize {
206 self.declared_cairo_classes.len()
207 + self.declared_sierra_classes.len()
208 + self
209 .system_contract_updates
210 .iter()
211 .map(|x| x.1.storage.len())
212 .sum::<usize>()
213 + self
214 .contract_updates
215 .iter()
216 .map(|x| {
217 x.1.storage.len()
218 + x.1.class.as_ref().map(|_| 1).unwrap_or_default()
219 + x.1.nonce.as_ref().map(|_| 1).unwrap_or_default()
220 })
221 .sum::<usize>()
222 }
223
224 pub fn contract_nonce(&self, contract: ContractAddress) -> Option<ContractNonce> {
231 self.contract_updates.get(&contract).and_then(|x| {
232 x.nonce.or_else(|| {
233 x.class.as_ref().and_then(|c| match c {
234 ContractClassUpdate::Deploy(_) => {
235 Some(ContractNonce::ZERO)
238 }
239 ContractClassUpdate::Replace(_) => None,
240 })
241 })
242 })
243 }
244
245 pub fn contract_class(&self, contract: ContractAddress) -> Option<ClassHash> {
248 self.contract_updates
249 .get(&contract)
250 .and_then(|x| x.class.as_ref().map(|x| x.class_hash()))
251 }
252
253 pub fn class_is_declared(&self, class: ClassHash) -> bool {
256 if self.declared_cairo_classes.contains(&class) {
257 return true;
258 }
259
260 self.declared_sierra_classes
261 .contains_key(&SierraHash(class.0))
262 }
263
264 pub fn storage_value(
269 &self,
270 contract: ContractAddress,
271 key: StorageAddress,
272 ) -> Option<StorageValue> {
273 self.contract_updates
274 .get(&contract)
275 .and_then(|update| {
276 update
277 .storage
278 .iter()
279 .find_map(|(k, v)| (k == &key).then_some(*v))
280 .or_else(|| {
281 update.class.as_ref().and_then(|c| match c {
282 ContractClassUpdate::Deploy(_) => Some(StorageValue::ZERO),
285 ContractClassUpdate::Replace(_) => None,
286 })
287 })
288 })
289 .or_else(|| {
290 self.system_contract_updates
291 .get(&contract)
292 .and_then(|update| {
293 update
294 .storage
295 .iter()
296 .find_map(|(k, v)| (k == &key).then_some(*v))
297 })
298 })
299 }
300
301 pub fn compute_state_diff_commitment(&self) -> StateDiffCommitment {
302 state_diff_commitment::compute(
303 &self.contract_updates,
304 &self.system_contract_updates,
305 &self.declared_cairo_classes,
306 &self.declared_sierra_classes,
307 &self.migrated_compiled_classes,
308 )
309 }
310
311 pub fn state_diff_length(&self) -> u64 {
312 let mut len = 0;
313 self.contract_updates.iter().for_each(|(_, update)| {
314 len += update.storage.len();
315 len += usize::from(update.nonce.is_some());
316 len += usize::from(update.class.is_some());
317 });
318 self.system_contract_updates.iter().for_each(|(_, update)| {
319 len += update.storage.len();
320 });
321 len += self.declared_cairo_classes.len()
322 + self.declared_sierra_classes.len()
323 + self.migrated_compiled_classes.len();
324 len.try_into().expect("ptr size is 64bits")
325 }
326
327 pub fn apply(mut self, other: &StateUpdate) -> Self {
329 self.block_hash = other.block_hash;
330 self.parent_state_commitment = other.parent_state_commitment;
331 self.state_commitment = other.state_commitment;
332
333 for (contract, other_update) in &other.contract_updates {
334 let update = self.contract_updates.entry(*contract).or_default();
335
336 for (key, value) in &other_update.storage {
338 update.storage.insert(*key, *value);
339 }
340
341 if let Some(class_update) = &other_update.class {
343 update.class = Some(*class_update);
344 }
345
346 if let Some(nonce) = other_update.nonce {
348 update.nonce = Some(nonce);
349 }
350 }
351
352 for (contract, other_update) in &other.system_contract_updates {
353 let update = self.system_contract_updates.entry(*contract).or_default();
354
355 for (key, value) in &other_update.storage {
357 update.storage.insert(*key, *value);
358 }
359 }
360
361 self.declared_cairo_classes
363 .extend(other.declared_cairo_classes.iter().copied());
364 self.declared_sierra_classes
365 .extend(other.declared_sierra_classes.iter().map(|(k, v)| (*k, *v)));
366
367 self
368 }
369}
370
371impl StateUpdateData {
372 pub fn compute_state_diff_commitment(&self) -> StateDiffCommitment {
373 state_diff_commitment::compute(
374 &self.contract_updates,
375 &self.system_contract_updates,
376 &self.declared_cairo_classes,
377 &self.declared_sierra_classes,
378 &self.migrated_compiled_classes,
379 )
380 }
381
382 pub fn is_empty(&self) -> bool {
383 self.contract_updates.is_empty()
384 && self.system_contract_updates.is_empty()
385 && self.declared_cairo_classes.is_empty()
386 && self.declared_sierra_classes.is_empty()
387 }
388
389 pub fn declared_classes(&self) -> DeclaredClasses {
390 DeclaredClasses {
391 sierra: self.declared_sierra_classes.clone(),
392 cairo: self.declared_cairo_classes.clone(),
393 }
394 }
395
396 pub fn state_diff_length(&self) -> u64 {
397 let mut len = 0;
398 self.contract_updates.iter().for_each(|(_, update)| {
399 len += update.storage.len();
400 len += usize::from(update.nonce.is_some());
401 len += usize::from(update.class.is_some());
402 });
403 self.system_contract_updates.iter().for_each(|(_, update)| {
404 len += update.storage.len();
405 });
406 len += self.declared_cairo_classes.len() + self.declared_sierra_classes.len();
407 len.try_into().expect("ptr size is 64bits")
408 }
409
410 pub fn as_ref(&self) -> StateUpdateRef<'_> {
411 StateUpdateRef::from(self)
412 }
413}
414
415impl From<StateUpdate> for StateUpdateData {
416 fn from(state_update: StateUpdate) -> Self {
417 Self {
418 contract_updates: state_update.contract_updates,
419 system_contract_updates: state_update.system_contract_updates,
420 declared_cairo_classes: state_update.declared_cairo_classes,
421 declared_sierra_classes: state_update.declared_sierra_classes,
422 migrated_compiled_classes: state_update.migrated_compiled_classes,
423 }
424 }
425}
426
427impl<'a> From<&'a StateUpdate> for StateUpdateRef<'a> {
428 fn from(state_update: &'a StateUpdate) -> Self {
429 Self {
430 contract_updates: state_update
431 .contract_updates
432 .iter()
433 .map(|(k, v)| {
434 (
435 k,
436 ContractUpdateRef {
437 storage: StorageRef::HashMap(&v.storage),
438 class: &v.class,
439 nonce: &v.nonce,
440 },
441 )
442 })
443 .collect(),
444 system_contract_updates: state_update
445 .system_contract_updates
446 .iter()
447 .map(|(k, v)| {
448 (
449 k,
450 SystemContractUpdateRef {
451 storage: StorageRef::HashMap(&v.storage),
452 },
453 )
454 })
455 .collect(),
456 declared_sierra_classes: &state_update.declared_sierra_classes,
457 migrated_compiled_classes: &state_update.migrated_compiled_classes,
458 }
459 }
460}
461
462impl<'a> From<&'a mut StateUpdate> for StateUpdateRef<'a> {
463 fn from(state_update: &'a mut StateUpdate) -> Self {
464 Self::from(state_update as &'a StateUpdate)
465 }
466}
467
468impl<'a> From<&'a StateUpdateData> for StateUpdateRef<'a> {
469 fn from(state_update: &'a StateUpdateData) -> Self {
470 Self {
471 contract_updates: state_update
472 .contract_updates
473 .iter()
474 .map(|(k, v)| {
475 (
476 k,
477 ContractUpdateRef {
478 storage: StorageRef::HashMap(&v.storage),
479 class: &v.class,
480 nonce: &v.nonce,
481 },
482 )
483 })
484 .collect(),
485 system_contract_updates: state_update
486 .system_contract_updates
487 .iter()
488 .map(|(k, v)| {
489 (
490 k,
491 SystemContractUpdateRef {
492 storage: StorageRef::HashMap(&v.storage),
493 },
494 )
495 })
496 .collect(),
497 declared_sierra_classes: &state_update.declared_sierra_classes,
498 migrated_compiled_classes: &state_update.migrated_compiled_classes,
499 }
500 }
501}
502
503impl<'a> From<&'a mut StateUpdateData> for StateUpdateRef<'a> {
504 fn from(state_update: &'a mut StateUpdateData) -> Self {
505 Self::from(state_update as &'a StateUpdateData)
506 }
507}
508
509impl StorageRef<'_> {
510 pub fn iter(&self) -> StorageRefIter<'_> {
511 match self {
512 StorageRef::HashMap(map) => StorageRefIter::HashMap(map.iter()),
513 StorageRef::Vec(vec) => StorageRefIter::Vec(vec.iter()),
514 }
515 }
516
517 pub fn is_empty(&self) -> bool {
518 match self {
519 StorageRef::HashMap(map) => map.is_empty(),
520 StorageRef::Vec(vec) => vec.is_empty(),
521 }
522 }
523}
524
525impl<'a> From<&'a ContractUpdate> for ContractUpdateRef<'a> {
526 fn from(x: &'a ContractUpdate) -> Self {
527 ContractUpdateRef {
528 storage: (&x.storage).into(),
529 class: &x.class,
530 nonce: &x.nonce,
531 }
532 }
533}
534
535impl<'a> From<&'a SystemContractUpdate> for SystemContractUpdateRef<'a> {
536 fn from(x: &'a SystemContractUpdate) -> Self {
537 SystemContractUpdateRef {
538 storage: (&x.storage).into(),
539 }
540 }
541}
542
543impl<'a> From<&'a HashMap<StorageAddress, StorageValue>> for StorageRef<'a> {
544 fn from(x: &'a HashMap<StorageAddress, StorageValue>) -> Self {
545 StorageRef::HashMap(x)
546 }
547}
548
549impl<'a> From<&'a Vec<(StorageAddress, StorageValue)>> for StorageRef<'a> {
550 fn from(x: &'a Vec<(StorageAddress, StorageValue)>) -> Self {
551 StorageRef::Vec(x)
552 }
553}
554
555impl<'a> IntoIterator for &'a StorageRef<'a> {
556 type Item = (&'a StorageAddress, &'a StorageValue);
557 type IntoIter = StorageRefIter<'a>;
558
559 fn into_iter(self) -> Self::IntoIter {
560 self.iter()
561 }
562}
563
564impl<'a> Iterator for StorageRefIter<'a> {
565 type Item = (&'a StorageAddress, &'a StorageValue);
566
567 fn next(&mut self) -> Option<Self::Item> {
568 match self {
569 StorageRefIter::HashMap(iter) => iter.next(),
570 StorageRefIter::Vec(iter) => iter.next().map(|(k, v)| (k, v)),
571 }
572 }
573}
574
575mod state_diff_commitment {
576 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
577
578 use pathfinder_crypto::hash::PoseidonHasher;
579 use pathfinder_crypto::MontFelt;
580
581 use super::{ContractUpdate, SystemContractUpdate};
582 use crate::{
583 felt_bytes,
584 CasmHash,
585 ClassHash,
586 ContractAddress,
587 SierraHash,
588 StateDiffCommitment,
589 };
590
591 pub fn compute(
595 contract_updates: &HashMap<ContractAddress, ContractUpdate>,
596 system_contract_updates: &HashMap<ContractAddress, SystemContractUpdate>,
597 declared_cairo_classes: &HashSet<ClassHash>,
598 declared_sierra_classes: &HashMap<SierraHash, CasmHash>,
599 migrated_compiled_classes: &HashMap<SierraHash, CasmHash>,
600 ) -> StateDiffCommitment {
601 let mut hasher = PoseidonHasher::new();
602 hasher.write(felt_bytes!(b"STARKNET_STATE_DIFF0").into());
603 let deployed_contracts: BTreeMap<_, _> = contract_updates
605 .iter()
606 .filter_map(|(address, update)| {
607 update
608 .class
609 .as_ref()
610 .map(|update| (*address, update.class_hash()))
611 })
612 .collect();
613 hasher.write(MontFelt::from(deployed_contracts.len() as u64));
614 for (address, class_hash) in deployed_contracts {
615 hasher.write(MontFelt::from(address.0));
616 hasher.write(MontFelt::from(class_hash.0));
617 }
618 let declared_classes: BTreeSet<_> = declared_sierra_classes
620 .iter()
621 .chain(migrated_compiled_classes.iter())
622 .map(|(sierra, casm)| (*sierra, *casm))
623 .collect();
624 hasher.write(MontFelt::from(declared_classes.len() as u64));
625 for (sierra, casm) in declared_classes {
626 hasher.write(MontFelt::from(sierra.0));
627 hasher.write(MontFelt::from(casm.0));
628 }
629 let deprecated_declared_classes: BTreeSet<_> =
631 declared_cairo_classes.iter().copied().collect();
632 hasher.write(MontFelt::from(deprecated_declared_classes.len() as u64));
633 for class_hash in deprecated_declared_classes {
634 hasher.write(MontFelt::from(class_hash.0));
635 }
636 hasher.write(MontFelt::ONE);
637 hasher.write(MontFelt::ZERO);
638 let storage_diffs: BTreeMap<_, _> = contract_updates
640 .iter()
641 .map(|(address, update)| (address, &update.storage))
642 .chain(
643 system_contract_updates
644 .iter()
645 .map(|(address, update)| (address, &update.storage)),
646 )
647 .filter_map(|(address, storage)| {
648 if storage.is_empty() {
649 None
650 } else {
651 let updates: BTreeMap<_, _> =
652 storage.iter().map(|(key, value)| (*key, *value)).collect();
653 Some((*address, updates))
654 }
655 })
656 .collect();
657 hasher.write(MontFelt::from(storage_diffs.len() as u64));
658 for (address, updates) in storage_diffs {
659 hasher.write(MontFelt::from(address.0));
660 hasher.write(MontFelt::from(updates.len() as u64));
661 for (key, value) in updates {
662 hasher.write(MontFelt::from(key.0));
663 hasher.write(MontFelt::from(value.0));
664 }
665 }
666 let nonces: BTreeMap<_, _> = contract_updates
668 .iter()
669 .filter_map(|(address, update)| update.nonce.map(|nonce| (*address, nonce)))
670 .collect();
671 hasher.write(MontFelt::from(nonces.len() as u64));
672 for (address, nonce) in nonces {
673 hasher.write(MontFelt::from(address.0));
674 hasher.write(MontFelt::from(nonce.0));
675 }
676 StateDiffCommitment(hasher.finish().into())
677 }
678}
679
680#[derive(Debug, PartialEq)]
681pub enum ReverseContractUpdate {
682 Deleted,
683 Updated(ContractUpdate),
684}
685
686impl ReverseContractUpdate {
687 pub fn update_mut(&mut self) -> Option<&mut ContractUpdate> {
688 match self {
689 Self::Deleted => None,
690 Self::Updated(update) => Some(update),
691 }
692 }
693}
694
695#[derive(Clone, Debug, PartialEq)]
696pub struct DeclaredClasses {
697 pub sierra: HashMap<SierraHash, CasmHash>,
698 pub cairo: HashSet<ClassHash>,
699}
700
701impl DeclaredClasses {
702 pub fn is_empty(&self) -> bool {
703 self.len() == 0
704 }
705
706 pub fn len(&self) -> usize {
707 self.sierra.len() + self.cairo.len()
708 }
709}
710
711#[derive(Debug, thiserror::Error)]
712pub enum StateUpdateError {
713 #[error("Contract class hash missing for contract {0}")]
714 ContractClassHashMissing(ContractAddress),
715 #[error(transparent)]
716 StorageError(#[from] anyhow::Error),
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use crate::macro_prelude::*;
723
724 #[test]
725 fn change_count() {
726 let state_update = StateUpdate::default()
727 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
728 .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
729 .with_declared_cairo_class(class_hash!("0x3"))
730 .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
731 .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
732 .with_replaced_class(contract_address!("0x33"), class_hash!("0x35"))
733 .with_system_storage_update(
734 ContractAddress::ONE,
735 storage_address!("0x10"),
736 storage_value!("0x99"),
737 )
738 .with_storage_update(
739 contract_address!("0x33"),
740 storage_address!("0x10"),
741 storage_value!("0x99"),
742 );
743
744 assert_eq!(state_update.change_count(), 8);
745 }
746
747 #[test]
748 fn contract_nonce() {
749 let state_update = StateUpdate::default()
750 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
751 .with_deployed_contract(contract_address!("0x2"), class_hash!("0x4"))
752 .with_contract_nonce(contract_address!("0x10"), contract_nonce!("0x20"))
753 .with_deployed_contract(contract_address!("0x10"), class_hash!("0x12"))
754 .with_replaced_class(contract_address!("0x123"), class_hash!("0x1244"))
755 .with_replaced_class(contract_address!("0x1234"), class_hash!("0x12445"))
756 .with_contract_nonce(contract_address!("0x1234"), contract_nonce!("0x1111"));
757
758 assert!(state_update
759 .contract_nonce(contract_address_bytes!(b"not present"))
760 .is_none());
761
762 let result = state_update.contract_nonce(contract_address!("0x1"));
763 assert_eq!(result, Some(contract_nonce!("0x2")));
764
765 let result = state_update.contract_nonce(contract_address!("0x10"));
767 assert_eq!(result, Some(contract_nonce!("0x20")));
768
769 let result = state_update.contract_nonce(contract_address!("0x2"));
771 assert_eq!(result, Some(ContractNonce::ZERO));
772
773 let result = state_update.contract_nonce(contract_address!("0x1234"));
775 assert_eq!(result, Some(contract_nonce!("0x1111")));
776
777 assert!(state_update
779 .contract_nonce(contract_address!("0x123"))
780 .is_none());
781 }
782
783 mod storage_value {
784 use super::*;
785
786 #[test]
787 fn set() {
788 let c = contract_address!("0x1");
789 let k = storage_address!("0x2");
790 let v = storage_value!("0x3");
791 let state_update = StateUpdate::default().with_storage_update(c, k, v);
792 let result = state_update.storage_value(c, k);
793 assert_eq!(result, Some(v))
794 }
795
796 #[test]
797 fn not_set() {
798 let c = contract_address!("0x1");
799 let k = storage_address!("0x2");
800 let v = storage_value!("0x3");
801 let state_update = StateUpdate::default().with_storage_update(c, k, v);
802 let result = state_update.storage_value(contract_address!("0x4"), k);
803 assert!(result.is_none());
804
805 let result = state_update.storage_value(c, storage_address!("0x24"));
806 assert!(result.is_none());
807 }
808
809 #[test]
810 fn deployed_and_not_set() {
811 let c = contract_address!("0x1");
812 let state_update = StateUpdate::default().with_deployed_contract(c, class_hash!("0x1"));
813 let result = state_update.storage_value(c, storage_address!("0x2"));
814 assert_eq!(result, Some(StorageValue::ZERO));
815 }
816
817 #[test]
818 fn deployed_and_set() {
819 let c = contract_address!("0x1");
820 let k = storage_address!("0x2");
821 let v = storage_value!("0x3");
822 let state_update = StateUpdate::default()
823 .with_deployed_contract(c, class_hash!("0x1"))
824 .with_storage_update(c, k, v);
825 let result = state_update.storage_value(c, k);
826 assert_eq!(result, Some(v));
827 }
828
829 #[test]
830 fn replaced_and_not_set() {
831 let c = contract_address!("0x1");
832 let state_update = StateUpdate::default().with_replaced_class(c, class_hash!("0x1"));
833 let result = state_update.storage_value(c, storage_address!("0x2"));
834 assert!(result.is_none());
835 }
836
837 #[test]
838 fn replaced_and_set() {
839 let c = contract_address!("0x1");
840 let k = storage_address!("0x2");
841 let v = storage_value!("0x3");
842 let state_update = StateUpdate::default()
843 .with_replaced_class(c, class_hash!("0x1"))
844 .with_storage_update(c, k, v);
845 let result = state_update.storage_value(c, k);
846 assert_eq!(result, Some(v));
847 }
848
849 #[test]
850 fn system_contract_and_set() {
851 let c = contract_address!("0x1");
852 let k = storage_address!("0x2");
853 let v = storage_value!("0x3");
854 let state_update = StateUpdate::default().with_system_storage_update(c, k, v);
855 let result = state_update.storage_value(c, k);
856 assert_eq!(result, Some(v))
857 }
858
859 #[test]
860 fn system_contract_and_not_set() {
861 let c = contract_address!("0x1");
862 let k = storage_address!("0x2");
863 let v = storage_value!("0x3");
864 let state_update = StateUpdate::default().with_system_storage_update(c, k, v);
865 let result = state_update.storage_value(contract_address!("0x4"), k);
866 assert_eq!(result, None);
867 let result = state_update.storage_value(c, storage_address!("0x24"));
868 assert_eq!(result, None);
869 }
870 }
871
872 #[test]
873 fn class_is_declared() {
874 let cairo = class_hash_bytes!(b"cairo class");
875 let sierra = class_hash_bytes!(b"sierra class");
876
877 let state_update = StateUpdate::default()
878 .with_declared_cairo_class(cairo)
879 .with_declared_sierra_class(SierraHash(sierra.0), casm_hash_bytes!(b"anything"));
880
881 assert!(state_update.class_is_declared(cairo));
882 assert!(state_update.class_is_declared(sierra));
883 assert!(!state_update.class_is_declared(class_hash_bytes!(b"nope")));
884 }
885
886 #[test]
887 fn contract_class() {
888 let deployed = contract_address_bytes!(b"deployed");
889 let deployed_class = class_hash_bytes!(b"deployed class");
890 let replaced = contract_address_bytes!(b"replaced");
891 let replaced_class = class_hash_bytes!(b"replaced class");
892
893 let state_update = StateUpdate::default()
894 .with_deployed_contract(deployed, deployed_class)
895 .with_replaced_class(replaced, replaced_class);
896
897 let result = state_update.contract_class(deployed);
898 assert_eq!(result, Some(deployed_class));
899
900 let result = state_update.contract_class(replaced);
901 assert_eq!(result, Some(replaced_class));
902
903 assert!(state_update
904 .contract_class(contract_address_bytes!(b"bogus"))
905 .is_none());
906 }
907
908 #[test]
911 fn test_0_13_2_state_diff_commitment() {
912 let contract_updates: HashMap<_, _> = [
913 (
914 ContractAddress(0u64.into()),
915 ContractUpdate {
916 class: Some(ContractClassUpdate::Deploy(ClassHash(1u64.into()))),
917 ..Default::default()
918 },
919 ),
920 (
921 ContractAddress(2u64.into()),
922 ContractUpdate {
923 class: Some(ContractClassUpdate::Deploy(ClassHash(3u64.into()))),
924 ..Default::default()
925 },
926 ),
927 (
928 ContractAddress(4u64.into()),
929 ContractUpdate {
930 storage: [
931 (StorageAddress(5u64.into()), StorageValue(6u64.into())),
932 (StorageAddress(7u64.into()), StorageValue(8u64.into())),
933 ]
934 .iter()
935 .cloned()
936 .collect(),
937 ..Default::default()
938 },
939 ),
940 (
941 ContractAddress(9u64.into()),
942 ContractUpdate {
943 storage: [(StorageAddress(10u64.into()), StorageValue(11u64.into()))]
944 .iter()
945 .cloned()
946 .collect(),
947 ..Default::default()
948 },
949 ),
950 (
951 ContractAddress(17u64.into()),
952 ContractUpdate {
953 nonce: Some(ContractNonce(18u64.into())),
954 ..Default::default()
955 },
956 ),
957 (
958 ContractAddress(19u64.into()),
959 ContractUpdate {
960 class: Some(ContractClassUpdate::Replace(ClassHash(20u64.into()))),
961 ..Default::default()
962 },
963 ),
964 ]
965 .into_iter()
966 .collect();
967 let declared_sierra_classes: HashMap<_, _> = [
968 (SierraHash(12u64.into()), CasmHash(13u64.into())),
969 (SierraHash(14u64.into()), CasmHash(15u64.into())),
970 ]
971 .iter()
972 .cloned()
973 .collect();
974 let declared_cairo_classes: HashSet<_> =
975 [ClassHash(16u64.into())].iter().cloned().collect();
976
977 let expected_hash = StateDiffCommitment(felt!(
978 "0x0281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67"
979 ));
980
981 assert_eq!(
982 expected_hash,
983 state_diff_commitment::compute(
984 &contract_updates,
985 &Default::default(),
986 &declared_cairo_classes,
987 &declared_sierra_classes,
988 &Default::default(),
989 )
990 );
991 }
992
993 #[test]
996 fn test_0_13_2_state_diff_commitment_with_migrated_compiled_classes() {
997 let contract_updates: HashMap<_, _> = [
998 (
999 ContractAddress(0u64.into()),
1000 ContractUpdate {
1001 class: Some(ContractClassUpdate::Deploy(ClassHash(1u64.into()))),
1002 ..Default::default()
1003 },
1004 ),
1005 (
1006 ContractAddress(2u64.into()),
1007 ContractUpdate {
1008 class: Some(ContractClassUpdate::Deploy(ClassHash(3u64.into()))),
1009 ..Default::default()
1010 },
1011 ),
1012 (
1013 ContractAddress(4u64.into()),
1014 ContractUpdate {
1015 storage: [
1016 (StorageAddress(5u64.into()), StorageValue(6u64.into())),
1017 (StorageAddress(7u64.into()), StorageValue(8u64.into())),
1018 ]
1019 .iter()
1020 .cloned()
1021 .collect(),
1022 ..Default::default()
1023 },
1024 ),
1025 (
1026 ContractAddress(9u64.into()),
1027 ContractUpdate {
1028 storage: [(StorageAddress(10u64.into()), StorageValue(11u64.into()))]
1029 .iter()
1030 .cloned()
1031 .collect(),
1032 ..Default::default()
1033 },
1034 ),
1035 (
1036 ContractAddress(17u64.into()),
1037 ContractUpdate {
1038 nonce: Some(ContractNonce(18u64.into())),
1039 ..Default::default()
1040 },
1041 ),
1042 (
1043 ContractAddress(19u64.into()),
1044 ContractUpdate {
1045 class: Some(ContractClassUpdate::Replace(ClassHash(20u64.into()))),
1046 ..Default::default()
1047 },
1048 ),
1049 ]
1050 .into_iter()
1051 .collect();
1052 let declared_sierra_classes: HashMap<_, _> =
1053 [(SierraHash(12u64.into()), CasmHash(13u64.into()))]
1054 .iter()
1055 .cloned()
1056 .collect();
1057 let migrated_compiled_classes: HashMap<_, _> =
1058 [(SierraHash(14u64.into()), CasmHash(15u64.into()))]
1059 .iter()
1060 .cloned()
1061 .collect();
1062 let declared_cairo_classes: HashSet<_> =
1063 [ClassHash(16u64.into())].iter().cloned().collect();
1064
1065 let expected_hash = StateDiffCommitment(felt!(
1066 "0x0281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67"
1067 ));
1068
1069 assert_eq!(
1070 expected_hash,
1071 state_diff_commitment::compute(
1072 &contract_updates,
1073 &Default::default(),
1074 &declared_cairo_classes,
1075 &declared_sierra_classes,
1076 &migrated_compiled_classes,
1077 )
1078 );
1079 }
1080
1081 #[test]
1082 fn apply() {
1083 let state_update = StateUpdate::default()
1084 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
1085 .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
1086 .with_declared_cairo_class(class_hash!("0x3"))
1087 .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
1088 .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
1089 .with_replaced_class(contract_address!("0x33"), class_hash!("0x35"))
1090 .with_system_storage_update(
1091 ContractAddress::ONE,
1092 storage_address!("0x10"),
1093 storage_value!("0x99"),
1094 )
1095 .with_storage_update(
1096 contract_address!("0x33"),
1097 storage_address!("0x10"),
1098 storage_value!("0x99"),
1099 );
1100
1101 let second_state_update = StateUpdate::default()
1102 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x3"))
1103 .with_contract_nonce(contract_address!("0x5"), contract_nonce!("0x5"))
1104 .with_declared_cairo_class(class_hash!("0x6"))
1105 .with_declared_sierra_class(sierra_hash!("0x7"), casm_hash!("0x8"))
1106 .with_deployed_contract(contract_address!("0x9"), class_hash!("0x7"))
1107 .with_replaced_class(contract_address!("0x33"), class_hash!("0x37"))
1108 .with_system_storage_update(
1109 ContractAddress::ONE,
1110 storage_address!("0x11"),
1111 storage_value!("0x100"),
1112 )
1113 .with_storage_update(
1114 contract_address!("0x33"),
1115 storage_address!("0x10"),
1116 storage_value!("0x100"),
1117 );
1118
1119 let combined = state_update.apply(&second_state_update);
1120 let expected = StateUpdate::default()
1121 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x3"))
1122 .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
1123 .with_contract_nonce(contract_address!("0x5"), contract_nonce!("0x5"))
1124 .with_declared_cairo_class(class_hash!("0x3"))
1125 .with_declared_cairo_class(class_hash!("0x6"))
1126 .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
1127 .with_declared_sierra_class(sierra_hash!("0x7"), casm_hash!("0x8"))
1128 .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
1129 .with_deployed_contract(contract_address!("0x9"), class_hash!("0x7"))
1130 .with_replaced_class(contract_address!("0x33"), class_hash!("0x37"))
1131 .with_system_storage_update(
1132 ContractAddress::ONE,
1133 storage_address!("0x10"),
1134 storage_value!("0x99"),
1135 )
1136 .with_system_storage_update(
1137 ContractAddress::ONE,
1138 storage_address!("0x11"),
1139 storage_value!("0x100"),
1140 )
1141 .with_storage_update(
1142 contract_address!("0x33"),
1143 storage_address!("0x10"),
1144 storage_value!("0x100"),
1145 );
1146 assert_eq!(combined, expected);
1147 }
1148}