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