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}
29
30#[derive(Default, Debug, Clone, PartialEq, Dummy)]
31pub struct StateUpdateData {
32    pub contract_updates: HashMap<ContractAddress, ContractUpdate>,
33    pub system_contract_updates: HashMap<ContractAddress, SystemContractUpdate>,
34    pub declared_cairo_classes: HashSet<ClassHash>,
35    pub declared_sierra_classes: HashMap<SierraHash, CasmHash>,
36}
37
38#[derive(Default, Debug, Clone, PartialEq, Dummy)]
39pub struct ContractUpdate {
40    pub storage: HashMap<StorageAddress, StorageValue>,
41    /// The class associated with this update as the result of either a deploy
42    /// or class replacement transaction.
43    pub class: Option<ContractClassUpdate>,
44    pub nonce: Option<ContractNonce>,
45}
46
47#[derive(Default, Debug, Clone, PartialEq, Dummy)]
48pub struct SystemContractUpdate {
49    pub storage: HashMap<StorageAddress, StorageValue>,
50}
51
52#[derive(Debug, Copy, Clone, PartialEq, Dummy)]
53pub enum ContractClassUpdate {
54    Deploy(ClassHash),
55    Replace(ClassHash),
56}
57
58pub struct StateUpdateRef<'a> {
59    pub contract_updates: Vec<(&'a ContractAddress, ContractUpdateRef<'a>)>,
60    pub system_contract_updates: Vec<(&'a ContractAddress, SystemContractUpdateRef<'a>)>,
61    pub declared_sierra_classes: &'a HashMap<SierraHash, CasmHash>,
62}
63
64pub struct ContractUpdateRef<'a> {
65    pub storage: StorageRef<'a>,
66    pub class: &'a Option<ContractClassUpdate>,
67    pub nonce: &'a Option<ContractNonce>,
68}
69
70pub struct SystemContractUpdateRef<'a> {
71    pub storage: StorageRef<'a>,
72}
73
74#[derive(Copy, Clone)]
75pub enum StorageRef<'a> {
76    HashMap(&'a HashMap<StorageAddress, StorageValue>),
77    Vec(&'a Vec<(StorageAddress, StorageValue)>),
78}
79
80pub enum StorageRefIter<'a> {
81    HashMap(hash_map::Iter<'a, StorageAddress, StorageValue>),
82    Vec(slice::Iter<'a, (StorageAddress, StorageValue)>),
83}
84
85impl ContractUpdate {
86    pub fn replaced_class(&self) -> Option<&ClassHash> {
87        match &self.class {
88            Some(ContractClassUpdate::Replace(hash)) => Some(hash),
89            _ => None,
90        }
91    }
92
93    pub fn deployed_class(&self) -> Option<&ClassHash> {
94        match &self.class {
95            Some(ContractClassUpdate::Deploy(hash)) => Some(hash),
96            _ => None,
97        }
98    }
99}
100
101impl ContractClassUpdate {
102    pub fn class_hash(&self) -> ClassHash {
103        match self {
104            ContractClassUpdate::Deploy(x) => *x,
105            ContractClassUpdate::Replace(x) => *x,
106        }
107    }
108
109    pub fn is_replaced(&self) -> bool {
110        matches!(self, ContractClassUpdate::Replace(_))
111    }
112}
113
114impl StateUpdate {
115    pub fn with_block_hash(mut self, block_hash: BlockHash) -> Self {
116        self.block_hash = block_hash;
117        self
118    }
119
120    pub fn with_state_commitment(mut self, state_commitment: StateCommitment) -> Self {
121        self.state_commitment = state_commitment;
122        self
123    }
124
125    pub fn with_parent_state_commitment(
126        mut self,
127        parent_state_commitment: StateCommitment,
128    ) -> Self {
129        self.parent_state_commitment = parent_state_commitment;
130        self
131    }
132
133    pub fn with_contract_nonce(mut self, contract: ContractAddress, nonce: ContractNonce) -> Self {
134        self.contract_updates.entry(contract).or_default().nonce = Some(nonce);
135        self
136    }
137
138    pub fn with_storage_update(
139        mut self,
140        contract: ContractAddress,
141        key: StorageAddress,
142        value: StorageValue,
143    ) -> Self {
144        self.contract_updates
145            .entry(contract)
146            .or_default()
147            .storage
148            .insert(key, value);
149        self
150    }
151
152    pub fn with_system_storage_update(
153        mut self,
154        contract: ContractAddress,
155        key: StorageAddress,
156        value: StorageValue,
157    ) -> Self {
158        self.system_contract_updates
159            .entry(contract)
160            .or_default()
161            .storage
162            .insert(key, value);
163        self
164    }
165
166    pub fn with_deployed_contract(mut self, contract: ContractAddress, class: ClassHash) -> Self {
167        self.contract_updates.entry(contract).or_default().class =
168            Some(ContractClassUpdate::Deploy(class));
169        self
170    }
171
172    pub fn with_replaced_class(mut self, contract: ContractAddress, class: ClassHash) -> Self {
173        self.contract_updates.entry(contract).or_default().class =
174            Some(ContractClassUpdate::Replace(class));
175        self
176    }
177
178    pub fn with_declared_sierra_class(mut self, sierra: SierraHash, casm: CasmHash) -> Self {
179        self.declared_sierra_classes.insert(sierra, casm);
180        self
181    }
182
183    pub fn with_declared_cairo_class(mut self, cairo: ClassHash) -> Self {
184        self.declared_cairo_classes.insert(cairo);
185        self
186    }
187
188    /// The number of individual changes in this state update.
189    ///
190    /// The total amount of:
191    /// - system storage updates
192    /// - contract storage updates
193    /// - contract nonce updates
194    /// - contract deployments
195    /// - contract class replacements
196    /// - class declarations
197    pub fn change_count(&self) -> usize {
198        self.declared_cairo_classes.len()
199            + self.declared_sierra_classes.len()
200            + self
201                .system_contract_updates
202                .iter()
203                .map(|x| x.1.storage.len())
204                .sum::<usize>()
205            + self
206                .contract_updates
207                .iter()
208                .map(|x| {
209                    x.1.storage.len()
210                        + x.1.class.as_ref().map(|_| 1).unwrap_or_default()
211                        + x.1.nonce.as_ref().map(|_| 1).unwrap_or_default()
212                })
213                .sum::<usize>()
214    }
215
216    /// Returns the contract's new [nonce](ContractNonce) value if it exists in
217    /// this state update.
218    ///
219    /// Note that this will return [Some(ContractNonce::ZERO)] for a contract
220    /// that has been deployed, but without an explicit nonce update. This
221    /// is consistent with expectations.
222    pub fn contract_nonce(&self, contract: ContractAddress) -> Option<ContractNonce> {
223        self.contract_updates.get(&contract).and_then(|x| {
224            x.nonce.or_else(|| {
225                x.class.as_ref().and_then(|c| match c {
226                    ContractClassUpdate::Deploy(_) => {
227                        // The contract has been just deployed in the pending block, so
228                        // its nonce is zero.
229                        Some(ContractNonce::ZERO)
230                    }
231                    ContractClassUpdate::Replace(_) => None,
232                })
233            })
234        })
235    }
236
237    /// A contract's new class hash, if it was deployed or replaced in this
238    /// state update.
239    pub fn contract_class(&self, contract: ContractAddress) -> Option<ClassHash> {
240        self.contract_updates
241            .get(&contract)
242            .and_then(|x| x.class.as_ref().map(|x| x.class_hash()))
243    }
244
245    /// Returns true if the class was declared as either a cairo 0 or sierra
246    /// class.
247    pub fn class_is_declared(&self, class: ClassHash) -> bool {
248        if self.declared_cairo_classes.contains(&class) {
249            return true;
250        }
251
252        self.declared_sierra_classes
253            .contains_key(&SierraHash(class.0))
254    }
255
256    /// The new storage value if it exists in this state update.
257    ///
258    /// Note that this will also return the default zero value for a contract
259    /// that has been deployed, but without an explicit storage update.
260    pub fn storage_value(
261        &self,
262        contract: ContractAddress,
263        key: StorageAddress,
264    ) -> Option<StorageValue> {
265        self.contract_updates
266            .get(&contract)
267            .and_then(|update| {
268                update
269                    .storage
270                    .iter()
271                    .find_map(|(k, v)| (k == &key).then_some(*v))
272                    .or_else(|| {
273                        update.class.as_ref().and_then(|c| match c {
274                            // If the contract has been deployed in pending but the key has not been
275                            // set yet return the default value of zero.
276                            ContractClassUpdate::Deploy(_) => Some(StorageValue::ZERO),
277                            ContractClassUpdate::Replace(_) => None,
278                        })
279                    })
280            })
281            .or_else(|| {
282                self.system_contract_updates
283                    .get(&contract)
284                    .and_then(|update| {
285                        update
286                            .storage
287                            .iter()
288                            .find_map(|(k, v)| (k == &key).then_some(*v))
289                    })
290            })
291    }
292
293    pub fn compute_state_diff_commitment(&self) -> StateDiffCommitment {
294        state_diff_commitment::compute(
295            &self.contract_updates,
296            &self.system_contract_updates,
297            &self.declared_cairo_classes,
298            &self.declared_sierra_classes,
299        )
300    }
301
302    pub fn state_diff_length(&self) -> u64 {
303        let mut len = 0;
304        self.contract_updates.iter().for_each(|(_, update)| {
305            len += update.storage.len();
306            len += usize::from(update.nonce.is_some());
307            len += usize::from(update.class.is_some());
308        });
309        self.system_contract_updates.iter().for_each(|(_, update)| {
310            len += update.storage.len();
311        });
312        len += self.declared_cairo_classes.len() + self.declared_sierra_classes.len();
313        len.try_into().expect("ptr size is 64bits")
314    }
315}
316
317impl StateUpdateData {
318    pub fn compute_state_diff_commitment(&self) -> StateDiffCommitment {
319        state_diff_commitment::compute(
320            &self.contract_updates,
321            &self.system_contract_updates,
322            &self.declared_cairo_classes,
323            &self.declared_sierra_classes,
324        )
325    }
326
327    pub fn is_empty(&self) -> bool {
328        self.contract_updates.is_empty()
329            && self.system_contract_updates.is_empty()
330            && self.declared_cairo_classes.is_empty()
331            && self.declared_sierra_classes.is_empty()
332    }
333
334    pub fn declared_classes(&self) -> DeclaredClasses {
335        DeclaredClasses {
336            sierra: self.declared_sierra_classes.clone(),
337            cairo: self.declared_cairo_classes.clone(),
338        }
339    }
340
341    pub fn state_diff_length(&self) -> usize {
342        let mut len = 0;
343        self.contract_updates.iter().for_each(|(_, update)| {
344            len += update.storage.len();
345            len += usize::from(update.nonce.is_some());
346            len += usize::from(update.class.is_some());
347        });
348        self.system_contract_updates.iter().for_each(|(_, update)| {
349            len += update.storage.len();
350        });
351        len += self.declared_cairo_classes.len() + self.declared_sierra_classes.len();
352        len
353    }
354}
355
356impl From<StateUpdate> for StateUpdateData {
357    fn from(state_update: StateUpdate) -> Self {
358        Self {
359            contract_updates: state_update.contract_updates,
360            system_contract_updates: state_update.system_contract_updates,
361            declared_cairo_classes: state_update.declared_cairo_classes,
362            declared_sierra_classes: state_update.declared_sierra_classes,
363        }
364    }
365}
366
367impl<'a> From<&'a StateUpdate> for StateUpdateRef<'a> {
368    fn from(state_update: &'a StateUpdate) -> Self {
369        Self {
370            contract_updates: state_update
371                .contract_updates
372                .iter()
373                .map(|(k, v)| {
374                    (
375                        k,
376                        ContractUpdateRef {
377                            storage: StorageRef::HashMap(&v.storage),
378                            class: &v.class,
379                            nonce: &v.nonce,
380                        },
381                    )
382                })
383                .collect(),
384            system_contract_updates: state_update
385                .system_contract_updates
386                .iter()
387                .map(|(k, v)| {
388                    (
389                        k,
390                        SystemContractUpdateRef {
391                            storage: StorageRef::HashMap(&v.storage),
392                        },
393                    )
394                })
395                .collect(),
396            declared_sierra_classes: &state_update.declared_sierra_classes,
397        }
398    }
399}
400
401impl<'a> From<&'a mut StateUpdate> for StateUpdateRef<'a> {
402    fn from(state_update: &'a mut StateUpdate) -> Self {
403        Self::from(state_update as &'a StateUpdate)
404    }
405}
406
407impl<'a> From<&'a StateUpdateData> for StateUpdateRef<'a> {
408    fn from(state_update: &'a StateUpdateData) -> Self {
409        Self {
410            contract_updates: state_update
411                .contract_updates
412                .iter()
413                .map(|(k, v)| {
414                    (
415                        k,
416                        ContractUpdateRef {
417                            storage: StorageRef::HashMap(&v.storage),
418                            class: &v.class,
419                            nonce: &v.nonce,
420                        },
421                    )
422                })
423                .collect(),
424            system_contract_updates: state_update
425                .system_contract_updates
426                .iter()
427                .map(|(k, v)| {
428                    (
429                        k,
430                        SystemContractUpdateRef {
431                            storage: StorageRef::HashMap(&v.storage),
432                        },
433                    )
434                })
435                .collect(),
436            declared_sierra_classes: &state_update.declared_sierra_classes,
437        }
438    }
439}
440
441impl<'a> From<&'a mut StateUpdateData> for StateUpdateRef<'a> {
442    fn from(state_update: &'a mut StateUpdateData) -> Self {
443        Self::from(state_update as &'a StateUpdateData)
444    }
445}
446
447impl StorageRef<'_> {
448    pub fn iter(&self) -> StorageRefIter<'_> {
449        match self {
450            StorageRef::HashMap(map) => StorageRefIter::HashMap(map.iter()),
451            StorageRef::Vec(vec) => StorageRefIter::Vec(vec.iter()),
452        }
453    }
454
455    pub fn is_empty(&self) -> bool {
456        match self {
457            StorageRef::HashMap(map) => map.is_empty(),
458            StorageRef::Vec(vec) => vec.is_empty(),
459        }
460    }
461}
462
463impl<'a> From<&'a ContractUpdate> for ContractUpdateRef<'a> {
464    fn from(x: &'a ContractUpdate) -> Self {
465        ContractUpdateRef {
466            storage: (&x.storage).into(),
467            class: &x.class,
468            nonce: &x.nonce,
469        }
470    }
471}
472
473impl<'a> From<&'a SystemContractUpdate> for SystemContractUpdateRef<'a> {
474    fn from(x: &'a SystemContractUpdate) -> Self {
475        SystemContractUpdateRef {
476            storage: (&x.storage).into(),
477        }
478    }
479}
480
481impl<'a> From<&'a HashMap<StorageAddress, StorageValue>> for StorageRef<'a> {
482    fn from(x: &'a HashMap<StorageAddress, StorageValue>) -> Self {
483        StorageRef::HashMap(x)
484    }
485}
486
487impl<'a> From<&'a Vec<(StorageAddress, StorageValue)>> for StorageRef<'a> {
488    fn from(x: &'a Vec<(StorageAddress, StorageValue)>) -> Self {
489        StorageRef::Vec(x)
490    }
491}
492
493impl<'a> IntoIterator for &'a StorageRef<'a> {
494    type Item = (&'a StorageAddress, &'a StorageValue);
495    type IntoIter = StorageRefIter<'a>;
496
497    fn into_iter(self) -> Self::IntoIter {
498        self.iter()
499    }
500}
501
502impl<'a> Iterator for StorageRefIter<'a> {
503    type Item = (&'a StorageAddress, &'a StorageValue);
504
505    fn next(&mut self) -> Option<Self::Item> {
506        match self {
507            StorageRefIter::HashMap(iter) => iter.next(),
508            StorageRefIter::Vec(iter) => iter.next().map(|(k, v)| (k, v)),
509        }
510    }
511}
512
513mod state_diff_commitment {
514    use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
515
516    use pathfinder_crypto::hash::PoseidonHasher;
517    use pathfinder_crypto::MontFelt;
518
519    use super::{ContractUpdate, SystemContractUpdate};
520    use crate::{
521        felt_bytes,
522        CasmHash,
523        ClassHash,
524        ContractAddress,
525        SierraHash,
526        StateDiffCommitment,
527    };
528
529    /// Compute the state diff commitment used in block commitment signatures.
530    ///
531    /// 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).
532    pub fn compute(
533        contract_updates: &HashMap<ContractAddress, ContractUpdate>,
534        system_contract_updates: &HashMap<ContractAddress, SystemContractUpdate>,
535        declared_cairo_classes: &HashSet<ClassHash>,
536        declared_sierra_classes: &HashMap<SierraHash, CasmHash>,
537    ) -> StateDiffCommitment {
538        let mut hasher = PoseidonHasher::new();
539        hasher.write(felt_bytes!(b"STARKNET_STATE_DIFF0").into());
540        // Hash the deployed contracts.
541        let deployed_contracts: BTreeMap<_, _> = contract_updates
542            .iter()
543            .filter_map(|(address, update)| {
544                update
545                    .class
546                    .as_ref()
547                    .map(|update| (*address, update.class_hash()))
548            })
549            .collect();
550        hasher.write(MontFelt::from(deployed_contracts.len() as u64));
551        for (address, class_hash) in deployed_contracts {
552            hasher.write(MontFelt::from(address.0));
553            hasher.write(MontFelt::from(class_hash.0));
554        }
555        // Hash the declared classes.
556        let declared_classes: BTreeSet<_> = declared_sierra_classes
557            .iter()
558            .map(|(sierra, casm)| (*sierra, *casm))
559            .collect();
560        hasher.write(MontFelt::from(declared_classes.len() as u64));
561        for (sierra, casm) in declared_classes {
562            hasher.write(MontFelt::from(sierra.0));
563            hasher.write(MontFelt::from(casm.0));
564        }
565        // Hash the old declared classes.
566        let deprecated_declared_classes: BTreeSet<_> =
567            declared_cairo_classes.iter().copied().collect();
568        hasher.write(MontFelt::from(deprecated_declared_classes.len() as u64));
569        for class_hash in deprecated_declared_classes {
570            hasher.write(MontFelt::from(class_hash.0));
571        }
572        hasher.write(MontFelt::ONE);
573        hasher.write(MontFelt::ZERO);
574        // Hash the storage diffs.
575        let storage_diffs: BTreeMap<_, _> = contract_updates
576            .iter()
577            .map(|(address, update)| (address, &update.storage))
578            .chain(
579                system_contract_updates
580                    .iter()
581                    .map(|(address, update)| (address, &update.storage)),
582            )
583            .filter_map(|(address, storage)| {
584                if storage.is_empty() {
585                    None
586                } else {
587                    let updates: BTreeMap<_, _> =
588                        storage.iter().map(|(key, value)| (*key, *value)).collect();
589                    Some((*address, updates))
590                }
591            })
592            .collect();
593        hasher.write(MontFelt::from(storage_diffs.len() as u64));
594        for (address, updates) in storage_diffs {
595            hasher.write(MontFelt::from(address.0));
596            hasher.write(MontFelt::from(updates.len() as u64));
597            for (key, value) in updates {
598                hasher.write(MontFelt::from(key.0));
599                hasher.write(MontFelt::from(value.0));
600            }
601        }
602        // Hash the nonce updates.
603        let nonces: BTreeMap<_, _> = contract_updates
604            .iter()
605            .filter_map(|(address, update)| update.nonce.map(|nonce| (*address, nonce)))
606            .collect();
607        hasher.write(MontFelt::from(nonces.len() as u64));
608        for (address, nonce) in nonces {
609            hasher.write(MontFelt::from(address.0));
610            hasher.write(MontFelt::from(nonce.0));
611        }
612        StateDiffCommitment(hasher.finish().into())
613    }
614}
615
616#[derive(Debug, PartialEq)]
617pub enum ReverseContractUpdate {
618    Deleted,
619    Updated(ContractUpdate),
620}
621
622impl ReverseContractUpdate {
623    pub fn update_mut(&mut self) -> Option<&mut ContractUpdate> {
624        match self {
625            Self::Deleted => None,
626            Self::Updated(update) => Some(update),
627        }
628    }
629}
630
631#[derive(Clone, Debug, PartialEq)]
632pub struct DeclaredClasses {
633    pub sierra: HashMap<SierraHash, CasmHash>,
634    pub cairo: HashSet<ClassHash>,
635}
636
637impl DeclaredClasses {
638    pub fn is_empty(&self) -> bool {
639        self.len() == 0
640    }
641
642    pub fn len(&self) -> usize {
643        self.sierra.len() + self.cairo.len()
644    }
645}
646
647#[derive(Debug, thiserror::Error)]
648pub enum StateUpdateError {
649    #[error("Contract class hash missing for contract {0}")]
650    ContractClassHashMissing(ContractAddress),
651    #[error(transparent)]
652    StorageError(#[from] anyhow::Error),
653}
654
655#[cfg(test)]
656mod tests {
657    use super::*;
658    use crate::macro_prelude::*;
659
660    #[test]
661    fn change_count() {
662        let state_update = StateUpdate::default()
663            .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
664            .with_contract_nonce(contract_address!("0x4"), contract_nonce!("0x5"))
665            .with_declared_cairo_class(class_hash!("0x3"))
666            .with_declared_sierra_class(sierra_hash!("0x4"), casm_hash!("0x5"))
667            .with_deployed_contract(contract_address!("0x1"), class_hash!("0x3"))
668            .with_replaced_class(contract_address!("0x33"), class_hash!("0x35"))
669            .with_system_storage_update(
670                ContractAddress::ONE,
671                storage_address!("0x10"),
672                storage_value!("0x99"),
673            )
674            .with_storage_update(
675                contract_address!("0x33"),
676                storage_address!("0x10"),
677                storage_value!("0x99"),
678            );
679
680        assert_eq!(state_update.change_count(), 8);
681    }
682
683    #[test]
684    fn contract_nonce() {
685        let state_update = StateUpdate::default()
686            .with_contract_nonce(contract_address!("0x1"), contract_nonce!("0x2"))
687            .with_deployed_contract(contract_address!("0x2"), class_hash!("0x4"))
688            .with_contract_nonce(contract_address!("0x10"), contract_nonce!("0x20"))
689            .with_deployed_contract(contract_address!("0x10"), class_hash!("0x12"))
690            .with_replaced_class(contract_address!("0x123"), class_hash!("0x1244"))
691            .with_replaced_class(contract_address!("0x1234"), class_hash!("0x12445"))
692            .with_contract_nonce(contract_address!("0x1234"), contract_nonce!("0x1111"));
693
694        assert!(state_update
695            .contract_nonce(contract_address_bytes!(b"not present"))
696            .is_none());
697
698        let result = state_update.contract_nonce(contract_address!("0x1"));
699        assert_eq!(result, Some(contract_nonce!("0x2")));
700
701        // A newly deployed contract with an explicit nonce set.
702        let result = state_update.contract_nonce(contract_address!("0x10"));
703        assert_eq!(result, Some(contract_nonce!("0x20")));
704
705        // A newly deployed contract without an explicit nonce set should be zero
706        let result = state_update.contract_nonce(contract_address!("0x2"));
707        assert_eq!(result, Some(ContractNonce::ZERO));
708
709        // A replaced contract with an explicit nonce set.
710        let result = state_update.contract_nonce(contract_address!("0x1234"));
711        assert_eq!(result, Some(contract_nonce!("0x1111")));
712
713        // A replaced class without an explicit nonce.
714        assert!(state_update
715            .contract_nonce(contract_address!("0x123"))
716            .is_none());
717    }
718
719    mod storage_value {
720        use super::*;
721
722        #[test]
723        fn set() {
724            let c = contract_address!("0x1");
725            let k = storage_address!("0x2");
726            let v = storage_value!("0x3");
727            let state_update = StateUpdate::default().with_storage_update(c, k, v);
728            let result = state_update.storage_value(c, k);
729            assert_eq!(result, Some(v))
730        }
731
732        #[test]
733        fn not_set() {
734            let c = contract_address!("0x1");
735            let k = storage_address!("0x2");
736            let v = storage_value!("0x3");
737            let state_update = StateUpdate::default().with_storage_update(c, k, v);
738            let result = state_update.storage_value(contract_address!("0x4"), k);
739            assert!(result.is_none());
740
741            let result = state_update.storage_value(c, storage_address!("0x24"));
742            assert!(result.is_none());
743        }
744
745        #[test]
746        fn deployed_and_not_set() {
747            let c = contract_address!("0x1");
748            let state_update = StateUpdate::default().with_deployed_contract(c, class_hash!("0x1"));
749            let result = state_update.storage_value(c, storage_address!("0x2"));
750            assert_eq!(result, Some(StorageValue::ZERO));
751        }
752
753        #[test]
754        fn deployed_and_set() {
755            let c = contract_address!("0x1");
756            let k = storage_address!("0x2");
757            let v = storage_value!("0x3");
758            let state_update = StateUpdate::default()
759                .with_deployed_contract(c, class_hash!("0x1"))
760                .with_storage_update(c, k, v);
761            let result = state_update.storage_value(c, k);
762            assert_eq!(result, Some(v));
763        }
764
765        #[test]
766        fn replaced_and_not_set() {
767            let c = contract_address!("0x1");
768            let state_update = StateUpdate::default().with_replaced_class(c, class_hash!("0x1"));
769            let result = state_update.storage_value(c, storage_address!("0x2"));
770            assert!(result.is_none());
771        }
772
773        #[test]
774        fn replaced_and_set() {
775            let c = contract_address!("0x1");
776            let k = storage_address!("0x2");
777            let v = storage_value!("0x3");
778            let state_update = StateUpdate::default()
779                .with_replaced_class(c, class_hash!("0x1"))
780                .with_storage_update(c, k, v);
781            let result = state_update.storage_value(c, k);
782            assert_eq!(result, Some(v));
783        }
784
785        #[test]
786        fn system_contract_and_set() {
787            let c = contract_address!("0x1");
788            let k = storage_address!("0x2");
789            let v = storage_value!("0x3");
790            let state_update = StateUpdate::default().with_system_storage_update(c, k, v);
791            let result = state_update.storage_value(c, k);
792            assert_eq!(result, Some(v))
793        }
794
795        #[test]
796        fn system_contract_and_not_set() {
797            let c = contract_address!("0x1");
798            let k = storage_address!("0x2");
799            let v = storage_value!("0x3");
800            let state_update = StateUpdate::default().with_system_storage_update(c, k, v);
801            let result = state_update.storage_value(contract_address!("0x4"), k);
802            assert_eq!(result, None);
803            let result = state_update.storage_value(c, storage_address!("0x24"));
804            assert_eq!(result, None);
805        }
806    }
807
808    #[test]
809    fn class_is_declared() {
810        let cairo = class_hash_bytes!(b"cairo class");
811        let sierra = class_hash_bytes!(b"sierra class");
812
813        let state_update = StateUpdate::default()
814            .with_declared_cairo_class(cairo)
815            .with_declared_sierra_class(SierraHash(sierra.0), casm_hash_bytes!(b"anything"));
816
817        assert!(state_update.class_is_declared(cairo));
818        assert!(state_update.class_is_declared(sierra));
819        assert!(!state_update.class_is_declared(class_hash_bytes!(b"nope")));
820    }
821
822    #[test]
823    fn contract_class() {
824        let deployed = contract_address_bytes!(b"deployed");
825        let deployed_class = class_hash_bytes!(b"deployed class");
826        let replaced = contract_address_bytes!(b"replaced");
827        let replaced_class = class_hash_bytes!(b"replaced class");
828
829        let state_update = StateUpdate::default()
830            .with_deployed_contract(deployed, deployed_class)
831            .with_replaced_class(replaced, replaced_class);
832
833        let result = state_update.contract_class(deployed);
834        assert_eq!(result, Some(deployed_class));
835
836        let result = state_update.contract_class(replaced);
837        assert_eq!(result, Some(replaced_class));
838
839        assert!(state_update
840            .contract_class(contract_address_bytes!(b"bogus"))
841            .is_none());
842    }
843
844    /// Source:
845    /// https://github.com/starkware-libs/starknet-api/blob/5565e5282f5fead364a41e49c173940fd83dee00/src/block_hash/state_diff_hash_test.rs#L14
846    #[test]
847    fn test_0_13_2_state_diff_commitment() {
848        let contract_updates: HashMap<_, _> = [
849            (
850                ContractAddress(0u64.into()),
851                ContractUpdate {
852                    class: Some(ContractClassUpdate::Deploy(ClassHash(1u64.into()))),
853                    ..Default::default()
854                },
855            ),
856            (
857                ContractAddress(2u64.into()),
858                ContractUpdate {
859                    class: Some(ContractClassUpdate::Deploy(ClassHash(3u64.into()))),
860                    ..Default::default()
861                },
862            ),
863            (
864                ContractAddress(4u64.into()),
865                ContractUpdate {
866                    storage: [
867                        (StorageAddress(5u64.into()), StorageValue(6u64.into())),
868                        (StorageAddress(7u64.into()), StorageValue(8u64.into())),
869                    ]
870                    .iter()
871                    .cloned()
872                    .collect(),
873                    ..Default::default()
874                },
875            ),
876            (
877                ContractAddress(9u64.into()),
878                ContractUpdate {
879                    storage: [(StorageAddress(10u64.into()), StorageValue(11u64.into()))]
880                        .iter()
881                        .cloned()
882                        .collect(),
883                    ..Default::default()
884                },
885            ),
886            (
887                ContractAddress(17u64.into()),
888                ContractUpdate {
889                    nonce: Some(ContractNonce(18u64.into())),
890                    ..Default::default()
891                },
892            ),
893            (
894                ContractAddress(19u64.into()),
895                ContractUpdate {
896                    class: Some(ContractClassUpdate::Replace(ClassHash(20u64.into()))),
897                    ..Default::default()
898                },
899            ),
900        ]
901        .into_iter()
902        .collect();
903        let declared_sierra_classes: HashMap<_, _> = [
904            (SierraHash(12u64.into()), CasmHash(13u64.into())),
905            (SierraHash(14u64.into()), CasmHash(15u64.into())),
906        ]
907        .iter()
908        .cloned()
909        .collect();
910        let declared_cairo_classes: HashSet<_> =
911            [ClassHash(16u64.into())].iter().cloned().collect();
912
913        let expected_hash = StateDiffCommitment(felt!(
914            "0x0281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67"
915        ));
916
917        assert_eq!(
918            expected_hash,
919            state_diff_commitment::compute(
920                &contract_updates,
921                &Default::default(),
922                &declared_cairo_classes,
923                &declared_sierra_classes,
924            )
925        );
926    }
927}