pathfinder_common/
state_update.rs

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    /// The class associated with this update as the result of either a deploy
44    /// or class replacement transaction.
45    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    /// The number of individual changes in this state update.
197    ///
198    /// The total amount of:
199    /// - system storage updates
200    /// - contract storage updates
201    /// - contract nonce updates
202    /// - contract deployments
203    /// - contract class replacements
204    /// - class declarations
205    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    /// Returns the contract's new [nonce](ContractNonce) value if it exists in
225    /// this state update.
226    ///
227    /// Note that this will return [Some(ContractNonce::ZERO)] for a contract
228    /// that has been deployed, but without an explicit nonce update. This
229    /// is consistent with expectations.
230    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                        // The contract has been just deployed in the pending block, so
236                        // its nonce is zero.
237                        Some(ContractNonce::ZERO)
238                    }
239                    ContractClassUpdate::Replace(_) => None,
240                })
241            })
242        })
243    }
244
245    /// A contract's new class hash, if it was deployed or replaced in this
246    /// state update.
247    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    /// Returns true if the class was declared as either a cairo 0 or sierra
254    /// class.
255    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    /// The new storage value if it exists in this state update.
265    ///
266    /// Note that this will also return the default zero value for a contract
267    /// that has been deployed, but without an explicit storage update.
268    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                            // If the contract has been deployed in pending but the key has not been
283                            // set yet return the default value of zero.
284                            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    /// Apply another state update on top of this one.
328    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            // Merge storage updates.
337            for (key, value) in &other_update.storage {
338                update.storage.insert(*key, *value);
339            }
340
341            // Merge class updates.
342            if let Some(class_update) = &other_update.class {
343                update.class = Some(*class_update);
344            }
345
346            // Merge nonce updates.
347            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            // Merge storage updates.
356            for (key, value) in &other_update.storage {
357                update.storage.insert(*key, *value);
358            }
359        }
360
361        // Merge declared classes.
362        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    /// Compute the state diff commitment used in block commitment signatures.
588    ///
589    /// How to compute the value is documented in the [Starknet documentation](https://docs.starknet.io/architecture-and-concepts/network-architecture/block-structure/#state_diff_hash).
590    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        // Hash the deployed contracts.
600        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        // Hash the declared classes and the migrated compiled classes.
615        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        // Hash the old declared classes.
626        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        // Hash the storage diffs.
635        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        // Hash the nonce updates.
663        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        // A newly deployed contract with an explicit nonce set.
762        let result = state_update.contract_nonce(contract_address!("0x10"));
763        assert_eq!(result, Some(contract_nonce!("0x20")));
764
765        // A newly deployed contract without an explicit nonce set should be zero
766        let result = state_update.contract_nonce(contract_address!("0x2"));
767        assert_eq!(result, Some(ContractNonce::ZERO));
768
769        // A replaced contract with an explicit nonce set.
770        let result = state_update.contract_nonce(contract_address!("0x1234"));
771        assert_eq!(result, Some(contract_nonce!("0x1111")));
772
773        // A replaced class without an explicit nonce.
774        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    /// Source:
905    /// https://github.com/starkware-libs/starknet-api/blob/5565e5282f5fead364a41e49c173940fd83dee00/src/block_hash/state_diff_hash_test.rs#L14
906    #[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    /// Source:
990    /// https://github.com/starkware-libs/starknet-api/blob/5565e5282f5fead364a41e49c173940fd83dee00/src/block_hash/state_diff_hash_test.rs#L14
991    #[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}