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
411impl From<StateUpdate> for StateUpdateData {
412 fn from(state_update: StateUpdate) -> Self {
413 Self {
414 contract_updates: state_update.contract_updates,
415 system_contract_updates: state_update.system_contract_updates,
416 declared_cairo_classes: state_update.declared_cairo_classes,
417 declared_sierra_classes: state_update.declared_sierra_classes,
418 migrated_compiled_classes: state_update.migrated_compiled_classes,
419 }
420 }
421}
422
423impl<'a> From<&'a StateUpdate> for StateUpdateRef<'a> {
424 fn from(state_update: &'a StateUpdate) -> Self {
425 Self {
426 contract_updates: state_update
427 .contract_updates
428 .iter()
429 .map(|(k, v)| {
430 (
431 k,
432 ContractUpdateRef {
433 storage: StorageRef::HashMap(&v.storage),
434 class: &v.class,
435 nonce: &v.nonce,
436 },
437 )
438 })
439 .collect(),
440 system_contract_updates: state_update
441 .system_contract_updates
442 .iter()
443 .map(|(k, v)| {
444 (
445 k,
446 SystemContractUpdateRef {
447 storage: StorageRef::HashMap(&v.storage),
448 },
449 )
450 })
451 .collect(),
452 declared_sierra_classes: &state_update.declared_sierra_classes,
453 migrated_compiled_classes: &state_update.migrated_compiled_classes,
454 }
455 }
456}
457
458impl<'a> From<&'a mut StateUpdate> for StateUpdateRef<'a> {
459 fn from(state_update: &'a mut StateUpdate) -> Self {
460 Self::from(state_update as &'a StateUpdate)
461 }
462}
463
464impl<'a> From<&'a StateUpdateData> for StateUpdateRef<'a> {
465 fn from(state_update: &'a StateUpdateData) -> Self {
466 Self {
467 contract_updates: state_update
468 .contract_updates
469 .iter()
470 .map(|(k, v)| {
471 (
472 k,
473 ContractUpdateRef {
474 storage: StorageRef::HashMap(&v.storage),
475 class: &v.class,
476 nonce: &v.nonce,
477 },
478 )
479 })
480 .collect(),
481 system_contract_updates: state_update
482 .system_contract_updates
483 .iter()
484 .map(|(k, v)| {
485 (
486 k,
487 SystemContractUpdateRef {
488 storage: StorageRef::HashMap(&v.storage),
489 },
490 )
491 })
492 .collect(),
493 declared_sierra_classes: &state_update.declared_sierra_classes,
494 migrated_compiled_classes: &state_update.migrated_compiled_classes,
495 }
496 }
497}
498
499impl<'a> From<&'a mut StateUpdateData> for StateUpdateRef<'a> {
500 fn from(state_update: &'a mut StateUpdateData) -> Self {
501 Self::from(state_update as &'a StateUpdateData)
502 }
503}
504
505impl StorageRef<'_> {
506 pub fn iter(&self) -> StorageRefIter<'_> {
507 match self {
508 StorageRef::HashMap(map) => StorageRefIter::HashMap(map.iter()),
509 StorageRef::Vec(vec) => StorageRefIter::Vec(vec.iter()),
510 }
511 }
512
513 pub fn is_empty(&self) -> bool {
514 match self {
515 StorageRef::HashMap(map) => map.is_empty(),
516 StorageRef::Vec(vec) => vec.is_empty(),
517 }
518 }
519}
520
521impl<'a> From<&'a ContractUpdate> for ContractUpdateRef<'a> {
522 fn from(x: &'a ContractUpdate) -> Self {
523 ContractUpdateRef {
524 storage: (&x.storage).into(),
525 class: &x.class,
526 nonce: &x.nonce,
527 }
528 }
529}
530
531impl<'a> From<&'a SystemContractUpdate> for SystemContractUpdateRef<'a> {
532 fn from(x: &'a SystemContractUpdate) -> Self {
533 SystemContractUpdateRef {
534 storage: (&x.storage).into(),
535 }
536 }
537}
538
539impl<'a> From<&'a HashMap<StorageAddress, StorageValue>> for StorageRef<'a> {
540 fn from(x: &'a HashMap<StorageAddress, StorageValue>) -> Self {
541 StorageRef::HashMap(x)
542 }
543}
544
545impl<'a> From<&'a Vec<(StorageAddress, StorageValue)>> for StorageRef<'a> {
546 fn from(x: &'a Vec<(StorageAddress, StorageValue)>) -> Self {
547 StorageRef::Vec(x)
548 }
549}
550
551impl<'a> IntoIterator for &'a StorageRef<'a> {
552 type Item = (&'a StorageAddress, &'a StorageValue);
553 type IntoIter = StorageRefIter<'a>;
554
555 fn into_iter(self) -> Self::IntoIter {
556 self.iter()
557 }
558}
559
560impl<'a> Iterator for StorageRefIter<'a> {
561 type Item = (&'a StorageAddress, &'a StorageValue);
562
563 fn next(&mut self) -> Option<Self::Item> {
564 match self {
565 StorageRefIter::HashMap(iter) => iter.next(),
566 StorageRefIter::Vec(iter) => iter.next().map(|(k, v)| (k, v)),
567 }
568 }
569}
570
571mod state_diff_commitment {
572 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
573
574 use pathfinder_crypto::hash::PoseidonHasher;
575 use pathfinder_crypto::MontFelt;
576
577 use super::{ContractUpdate, SystemContractUpdate};
578 use crate::{
579 felt_bytes,
580 CasmHash,
581 ClassHash,
582 ContractAddress,
583 SierraHash,
584 StateDiffCommitment,
585 };
586
587 pub fn compute(
591 contract_updates: &HashMap<ContractAddress, ContractUpdate>,
592 system_contract_updates: &HashMap<ContractAddress, SystemContractUpdate>,
593 declared_cairo_classes: &HashSet<ClassHash>,
594 declared_sierra_classes: &HashMap<SierraHash, CasmHash>,
595 migrated_compiled_classes: &HashMap<SierraHash, CasmHash>,
596 ) -> StateDiffCommitment {
597 let mut hasher = PoseidonHasher::new();
598 hasher.write(felt_bytes!(b"STARKNET_STATE_DIFF0").into());
599 let deployed_contracts: BTreeMap<_, _> = contract_updates
601 .iter()
602 .filter_map(|(address, update)| {
603 update
604 .class
605 .as_ref()
606 .map(|update| (*address, update.class_hash()))
607 })
608 .collect();
609 hasher.write(MontFelt::from(deployed_contracts.len() as u64));
610 for (address, class_hash) in deployed_contracts {
611 hasher.write(MontFelt::from(address.0));
612 hasher.write(MontFelt::from(class_hash.0));
613 }
614 let declared_classes: BTreeSet<_> = declared_sierra_classes
616 .iter()
617 .chain(migrated_compiled_classes.iter())
618 .map(|(sierra, casm)| (*sierra, *casm))
619 .collect();
620 hasher.write(MontFelt::from(declared_classes.len() as u64));
621 for (sierra, casm) in declared_classes {
622 hasher.write(MontFelt::from(sierra.0));
623 hasher.write(MontFelt::from(casm.0));
624 }
625 let deprecated_declared_classes: BTreeSet<_> =
627 declared_cairo_classes.iter().copied().collect();
628 hasher.write(MontFelt::from(deprecated_declared_classes.len() as u64));
629 for class_hash in deprecated_declared_classes {
630 hasher.write(MontFelt::from(class_hash.0));
631 }
632 hasher.write(MontFelt::ONE);
633 hasher.write(MontFelt::ZERO);
634 let storage_diffs: BTreeMap<_, _> = contract_updates
636 .iter()
637 .map(|(address, update)| (address, &update.storage))
638 .chain(
639 system_contract_updates
640 .iter()
641 .map(|(address, update)| (address, &update.storage)),
642 )
643 .filter_map(|(address, storage)| {
644 if storage.is_empty() {
645 None
646 } else {
647 let updates: BTreeMap<_, _> =
648 storage.iter().map(|(key, value)| (*key, *value)).collect();
649 Some((*address, updates))
650 }
651 })
652 .collect();
653 hasher.write(MontFelt::from(storage_diffs.len() as u64));
654 for (address, updates) in storage_diffs {
655 hasher.write(MontFelt::from(address.0));
656 hasher.write(MontFelt::from(updates.len() as u64));
657 for (key, value) in updates {
658 hasher.write(MontFelt::from(key.0));
659 hasher.write(MontFelt::from(value.0));
660 }
661 }
662 let nonces: BTreeMap<_, _> = contract_updates
664 .iter()
665 .filter_map(|(address, update)| update.nonce.map(|nonce| (*address, nonce)))
666 .collect();
667 hasher.write(MontFelt::from(nonces.len() as u64));
668 for (address, nonce) in nonces {
669 hasher.write(MontFelt::from(address.0));
670 hasher.write(MontFelt::from(nonce.0));
671 }
672 StateDiffCommitment(hasher.finish().into())
673 }
674}
675
676#[derive(Debug, PartialEq)]
677pub enum ReverseContractUpdate {
678 Deleted,
679 Updated(ContractUpdate),
680}
681
682impl ReverseContractUpdate {
683 pub fn update_mut(&mut self) -> Option<&mut ContractUpdate> {
684 match self {
685 Self::Deleted => None,
686 Self::Updated(update) => Some(update),
687 }
688 }
689}
690
691#[derive(Clone, Debug, PartialEq)]
692pub struct DeclaredClasses {
693 pub sierra: HashMap<SierraHash, CasmHash>,
694 pub cairo: HashSet<ClassHash>,
695}
696
697impl DeclaredClasses {
698 pub fn is_empty(&self) -> bool {
699 self.len() == 0
700 }
701
702 pub fn len(&self) -> usize {
703 self.sierra.len() + self.cairo.len()
704 }
705}
706
707#[derive(Debug, thiserror::Error)]
708pub enum StateUpdateError {
709 #[error("Contract class hash missing for contract {0}")]
710 ContractClassHashMissing(ContractAddress),
711 #[error(transparent)]
712 StorageError(#[from] anyhow::Error),
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718 use crate::macro_prelude::*;
719
720 #[test]
721 fn change_count() {
722 let state_update = StateUpdate::default()
723 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
724 .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
725 .with_declared_cairo_class(class_hash!("0x3"))
726 .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
727 .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
728 .with_replaced_class(contract_address!("0x33"), class_hash!("0x35"))
729 .with_system_storage_update(
730 ContractAddress::ONE,
731 storage_address!("0x10"),
732 storage_value!("0x99"),
733 )
734 .with_storage_update(
735 contract_address!("0x33"),
736 storage_address!("0x10"),
737 storage_value!("0x99"),
738 );
739
740 assert_eq!(state_update.change_count(), 8);
741 }
742
743 #[test]
744 fn contract_nonce() {
745 let state_update = StateUpdate::default()
746 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
747 .with_deployed_contract(contract_address!("0x2"), class_hash!("0x4"))
748 .with_contract_nonce(contract_address!("0x10"), contract_nonce!("0x20"))
749 .with_deployed_contract(contract_address!("0x10"), class_hash!("0x12"))
750 .with_replaced_class(contract_address!("0x123"), class_hash!("0x1244"))
751 .with_replaced_class(contract_address!("0x1234"), class_hash!("0x12445"))
752 .with_contract_nonce(contract_address!("0x1234"), contract_nonce!("0x1111"));
753
754 assert!(state_update
755 .contract_nonce(contract_address_bytes!(b"not present"))
756 .is_none());
757
758 let result = state_update.contract_nonce(contract_address!("0x1"));
759 assert_eq!(result, Some(contract_nonce!("0x2")));
760
761 let result = state_update.contract_nonce(contract_address!("0x10"));
763 assert_eq!(result, Some(contract_nonce!("0x20")));
764
765 let result = state_update.contract_nonce(contract_address!("0x2"));
767 assert_eq!(result, Some(ContractNonce::ZERO));
768
769 let result = state_update.contract_nonce(contract_address!("0x1234"));
771 assert_eq!(result, Some(contract_nonce!("0x1111")));
772
773 assert!(state_update
775 .contract_nonce(contract_address!("0x123"))
776 .is_none());
777 }
778
779 mod storage_value {
780 use super::*;
781
782 #[test]
783 fn set() {
784 let c = contract_address!("0x1");
785 let k = storage_address!("0x2");
786 let v = storage_value!("0x3");
787 let state_update = StateUpdate::default().with_storage_update(c, k, v);
788 let result = state_update.storage_value(c, k);
789 assert_eq!(result, Some(v))
790 }
791
792 #[test]
793 fn not_set() {
794 let c = contract_address!("0x1");
795 let k = storage_address!("0x2");
796 let v = storage_value!("0x3");
797 let state_update = StateUpdate::default().with_storage_update(c, k, v);
798 let result = state_update.storage_value(contract_address!("0x4"), k);
799 assert!(result.is_none());
800
801 let result = state_update.storage_value(c, storage_address!("0x24"));
802 assert!(result.is_none());
803 }
804
805 #[test]
806 fn deployed_and_not_set() {
807 let c = contract_address!("0x1");
808 let state_update = StateUpdate::default().with_deployed_contract(c, class_hash!("0x1"));
809 let result = state_update.storage_value(c, storage_address!("0x2"));
810 assert_eq!(result, Some(StorageValue::ZERO));
811 }
812
813 #[test]
814 fn deployed_and_set() {
815 let c = contract_address!("0x1");
816 let k = storage_address!("0x2");
817 let v = storage_value!("0x3");
818 let state_update = StateUpdate::default()
819 .with_deployed_contract(c, class_hash!("0x1"))
820 .with_storage_update(c, k, v);
821 let result = state_update.storage_value(c, k);
822 assert_eq!(result, Some(v));
823 }
824
825 #[test]
826 fn replaced_and_not_set() {
827 let c = contract_address!("0x1");
828 let state_update = StateUpdate::default().with_replaced_class(c, class_hash!("0x1"));
829 let result = state_update.storage_value(c, storage_address!("0x2"));
830 assert!(result.is_none());
831 }
832
833 #[test]
834 fn replaced_and_set() {
835 let c = contract_address!("0x1");
836 let k = storage_address!("0x2");
837 let v = storage_value!("0x3");
838 let state_update = StateUpdate::default()
839 .with_replaced_class(c, class_hash!("0x1"))
840 .with_storage_update(c, k, v);
841 let result = state_update.storage_value(c, k);
842 assert_eq!(result, Some(v));
843 }
844
845 #[test]
846 fn system_contract_and_set() {
847 let c = contract_address!("0x1");
848 let k = storage_address!("0x2");
849 let v = storage_value!("0x3");
850 let state_update = StateUpdate::default().with_system_storage_update(c, k, v);
851 let result = state_update.storage_value(c, k);
852 assert_eq!(result, Some(v))
853 }
854
855 #[test]
856 fn system_contract_and_not_set() {
857 let c = contract_address!("0x1");
858 let k = storage_address!("0x2");
859 let v = storage_value!("0x3");
860 let state_update = StateUpdate::default().with_system_storage_update(c, k, v);
861 let result = state_update.storage_value(contract_address!("0x4"), k);
862 assert_eq!(result, None);
863 let result = state_update.storage_value(c, storage_address!("0x24"));
864 assert_eq!(result, None);
865 }
866 }
867
868 #[test]
869 fn class_is_declared() {
870 let cairo = class_hash_bytes!(b"cairo class");
871 let sierra = class_hash_bytes!(b"sierra class");
872
873 let state_update = StateUpdate::default()
874 .with_declared_cairo_class(cairo)
875 .with_declared_sierra_class(SierraHash(sierra.0), casm_hash_bytes!(b"anything"));
876
877 assert!(state_update.class_is_declared(cairo));
878 assert!(state_update.class_is_declared(sierra));
879 assert!(!state_update.class_is_declared(class_hash_bytes!(b"nope")));
880 }
881
882 #[test]
883 fn contract_class() {
884 let deployed = contract_address_bytes!(b"deployed");
885 let deployed_class = class_hash_bytes!(b"deployed class");
886 let replaced = contract_address_bytes!(b"replaced");
887 let replaced_class = class_hash_bytes!(b"replaced class");
888
889 let state_update = StateUpdate::default()
890 .with_deployed_contract(deployed, deployed_class)
891 .with_replaced_class(replaced, replaced_class);
892
893 let result = state_update.contract_class(deployed);
894 assert_eq!(result, Some(deployed_class));
895
896 let result = state_update.contract_class(replaced);
897 assert_eq!(result, Some(replaced_class));
898
899 assert!(state_update
900 .contract_class(contract_address_bytes!(b"bogus"))
901 .is_none());
902 }
903
904 #[test]
907 fn test_0_13_2_state_diff_commitment() {
908 let contract_updates: HashMap<_, _> = [
909 (
910 ContractAddress(0u64.into()),
911 ContractUpdate {
912 class: Some(ContractClassUpdate::Deploy(ClassHash(1u64.into()))),
913 ..Default::default()
914 },
915 ),
916 (
917 ContractAddress(2u64.into()),
918 ContractUpdate {
919 class: Some(ContractClassUpdate::Deploy(ClassHash(3u64.into()))),
920 ..Default::default()
921 },
922 ),
923 (
924 ContractAddress(4u64.into()),
925 ContractUpdate {
926 storage: [
927 (StorageAddress(5u64.into()), StorageValue(6u64.into())),
928 (StorageAddress(7u64.into()), StorageValue(8u64.into())),
929 ]
930 .iter()
931 .cloned()
932 .collect(),
933 ..Default::default()
934 },
935 ),
936 (
937 ContractAddress(9u64.into()),
938 ContractUpdate {
939 storage: [(StorageAddress(10u64.into()), StorageValue(11u64.into()))]
940 .iter()
941 .cloned()
942 .collect(),
943 ..Default::default()
944 },
945 ),
946 (
947 ContractAddress(17u64.into()),
948 ContractUpdate {
949 nonce: Some(ContractNonce(18u64.into())),
950 ..Default::default()
951 },
952 ),
953 (
954 ContractAddress(19u64.into()),
955 ContractUpdate {
956 class: Some(ContractClassUpdate::Replace(ClassHash(20u64.into()))),
957 ..Default::default()
958 },
959 ),
960 ]
961 .into_iter()
962 .collect();
963 let declared_sierra_classes: HashMap<_, _> = [
964 (SierraHash(12u64.into()), CasmHash(13u64.into())),
965 (SierraHash(14u64.into()), CasmHash(15u64.into())),
966 ]
967 .iter()
968 .cloned()
969 .collect();
970 let declared_cairo_classes: HashSet<_> =
971 [ClassHash(16u64.into())].iter().cloned().collect();
972
973 let expected_hash = StateDiffCommitment(felt!(
974 "0x0281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67"
975 ));
976
977 assert_eq!(
978 expected_hash,
979 state_diff_commitment::compute(
980 &contract_updates,
981 &Default::default(),
982 &declared_cairo_classes,
983 &declared_sierra_classes,
984 &Default::default(),
985 )
986 );
987 }
988
989 #[test]
992 fn test_0_13_2_state_diff_commitment_with_migrated_compiled_classes() {
993 let contract_updates: HashMap<_, _> = [
994 (
995 ContractAddress(0u64.into()),
996 ContractUpdate {
997 class: Some(ContractClassUpdate::Deploy(ClassHash(1u64.into()))),
998 ..Default::default()
999 },
1000 ),
1001 (
1002 ContractAddress(2u64.into()),
1003 ContractUpdate {
1004 class: Some(ContractClassUpdate::Deploy(ClassHash(3u64.into()))),
1005 ..Default::default()
1006 },
1007 ),
1008 (
1009 ContractAddress(4u64.into()),
1010 ContractUpdate {
1011 storage: [
1012 (StorageAddress(5u64.into()), StorageValue(6u64.into())),
1013 (StorageAddress(7u64.into()), StorageValue(8u64.into())),
1014 ]
1015 .iter()
1016 .cloned()
1017 .collect(),
1018 ..Default::default()
1019 },
1020 ),
1021 (
1022 ContractAddress(9u64.into()),
1023 ContractUpdate {
1024 storage: [(StorageAddress(10u64.into()), StorageValue(11u64.into()))]
1025 .iter()
1026 .cloned()
1027 .collect(),
1028 ..Default::default()
1029 },
1030 ),
1031 (
1032 ContractAddress(17u64.into()),
1033 ContractUpdate {
1034 nonce: Some(ContractNonce(18u64.into())),
1035 ..Default::default()
1036 },
1037 ),
1038 (
1039 ContractAddress(19u64.into()),
1040 ContractUpdate {
1041 class: Some(ContractClassUpdate::Replace(ClassHash(20u64.into()))),
1042 ..Default::default()
1043 },
1044 ),
1045 ]
1046 .into_iter()
1047 .collect();
1048 let declared_sierra_classes: HashMap<_, _> =
1049 [(SierraHash(12u64.into()), CasmHash(13u64.into()))]
1050 .iter()
1051 .cloned()
1052 .collect();
1053 let migrated_compiled_classes: HashMap<_, _> =
1054 [(SierraHash(14u64.into()), CasmHash(15u64.into()))]
1055 .iter()
1056 .cloned()
1057 .collect();
1058 let declared_cairo_classes: HashSet<_> =
1059 [ClassHash(16u64.into())].iter().cloned().collect();
1060
1061 let expected_hash = StateDiffCommitment(felt!(
1062 "0x0281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67"
1063 ));
1064
1065 assert_eq!(
1066 expected_hash,
1067 state_diff_commitment::compute(
1068 &contract_updates,
1069 &Default::default(),
1070 &declared_cairo_classes,
1071 &declared_sierra_classes,
1072 &migrated_compiled_classes,
1073 )
1074 );
1075 }
1076
1077 #[test]
1078 fn apply() {
1079 let state_update = StateUpdate::default()
1080 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
1081 .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
1082 .with_declared_cairo_class(class_hash!("0x3"))
1083 .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
1084 .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
1085 .with_replaced_class(contract_address!("0x33"), class_hash!("0x35"))
1086 .with_system_storage_update(
1087 ContractAddress::ONE,
1088 storage_address!("0x10"),
1089 storage_value!("0x99"),
1090 )
1091 .with_storage_update(
1092 contract_address!("0x33"),
1093 storage_address!("0x10"),
1094 storage_value!("0x99"),
1095 );
1096
1097 let second_state_update = StateUpdate::default()
1098 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x3"))
1099 .with_contract_nonce(contract_address!("0x5"), contract_nonce!("0x5"))
1100 .with_declared_cairo_class(class_hash!("0x6"))
1101 .with_declared_sierra_class(sierra_hash!("0x7"), casm_hash!("0x8"))
1102 .with_deployed_contract(contract_address!("0x9"), class_hash!("0x7"))
1103 .with_replaced_class(contract_address!("0x33"), class_hash!("0x37"))
1104 .with_system_storage_update(
1105 ContractAddress::ONE,
1106 storage_address!("0x11"),
1107 storage_value!("0x100"),
1108 )
1109 .with_storage_update(
1110 contract_address!("0x33"),
1111 storage_address!("0x10"),
1112 storage_value!("0x100"),
1113 );
1114
1115 let combined = state_update.apply(&second_state_update);
1116 let expected = StateUpdate::default()
1117 .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x3"))
1118 .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
1119 .with_contract_nonce(contract_address!("0x5"), contract_nonce!("0x5"))
1120 .with_declared_cairo_class(class_hash!("0x3"))
1121 .with_declared_cairo_class(class_hash!("0x6"))
1122 .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
1123 .with_declared_sierra_class(sierra_hash!("0x7"), casm_hash!("0x8"))
1124 .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
1125 .with_deployed_contract(contract_address!("0x9"), class_hash!("0x7"))
1126 .with_replaced_class(contract_address!("0x33"), class_hash!("0x37"))
1127 .with_system_storage_update(
1128 ContractAddress::ONE,
1129 storage_address!("0x10"),
1130 storage_value!("0x99"),
1131 )
1132 .with_system_storage_update(
1133 ContractAddress::ONE,
1134 storage_address!("0x11"),
1135 storage_value!("0x100"),
1136 )
1137 .with_storage_update(
1138 contract_address!("0x33"),
1139 storage_address!("0x10"),
1140 storage_value!("0x100"),
1141 );
1142 assert_eq!(combined, expected);
1143 }
1144}