miden_objects/account/delta/
mod.rs

1use alloc::string::ToString;
2
3use super::{
4    Account, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable,
5    Word, ZERO,
6};
7use crate::AccountDeltaError;
8
9mod storage;
10pub use storage::{AccountStorageDelta, StorageMapDelta};
11
12mod vault;
13pub use vault::{
14    AccountVaultDelta, FungibleAssetDelta, NonFungibleAssetDelta, NonFungibleDeltaAction,
15};
16
17// ACCOUNT DELTA
18// ================================================================================================
19
20/// [AccountDelta] stores the differences between two account states.
21///
22/// The differences are represented as follows:
23/// - storage: an [AccountStorageDelta] that contains the changes to the account storage.
24/// - vault: an [AccountVaultDelta] object that contains the changes to the account vault.
25/// - nonce: if the nonce of the account has changed, the new nonce is stored here.
26///
27/// TODO: add ability to trace account code updates.
28#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct AccountDelta {
30    storage: AccountStorageDelta,
31    vault: AccountVaultDelta,
32    nonce: Option<Felt>,
33}
34
35impl AccountDelta {
36    // CONSTRUCTOR
37    // --------------------------------------------------------------------------------------------
38    /// Returns new [AccountDelta] instantiated from the provided components.
39    ///
40    /// # Errors
41    ///
42    /// - Returns an error if storage or vault were updated, but the nonce was either not updated or
43    ///   set to 0.
44    pub fn new(
45        storage: AccountStorageDelta,
46        vault: AccountVaultDelta,
47        nonce: Option<Felt>,
48    ) -> Result<Self, AccountDeltaError> {
49        // nonce must be updated if either account storage or vault were updated
50        validate_nonce(nonce, &storage, &vault)?;
51
52        Ok(Self { storage, vault, nonce })
53    }
54
55    /// Merge another [AccountDelta] into this one.
56    pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
57        match (&mut self.nonce, other.nonce) {
58            (Some(old), Some(new)) if new.as_int() <= old.as_int() => {
59                return Err(AccountDeltaError::InconsistentNonceUpdate(format!(
60                    "new nonce {new} is not larger than the old nonce {old}"
61                )))
62            },
63            // Incoming nonce takes precedence.
64            (old, new) => *old = new.or(*old),
65        };
66        self.storage.merge(other.storage)?;
67        self.vault.merge(other.vault)
68    }
69
70    // PUBLIC ACCESSORS
71    // --------------------------------------------------------------------------------------------
72
73    /// Returns true if this account delta does not contain any updates.
74    pub fn is_empty(&self) -> bool {
75        self.storage.is_empty() && self.vault.is_empty()
76    }
77
78    /// Returns storage updates for this account delta.
79    pub fn storage(&self) -> &AccountStorageDelta {
80        &self.storage
81    }
82
83    /// Returns vault updates for this account delta.
84    pub fn vault(&self) -> &AccountVaultDelta {
85        &self.vault
86    }
87
88    /// Returns the new nonce, if the nonce was changed.
89    pub fn nonce(&self) -> Option<Felt> {
90        self.nonce
91    }
92
93    /// Converts this storage delta into individual delta components.
94    pub fn into_parts(self) -> (AccountStorageDelta, AccountVaultDelta, Option<Felt>) {
95        (self.storage, self.vault, self.nonce)
96    }
97}
98
99/// Describes the details of an account state transition resulting from applying a transaction to
100/// the account.
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum AccountUpdateDetails {
103    /// Account is private (no on-chain state change).
104    Private,
105
106    /// The whole state is needed for new accounts.
107    New(Account),
108
109    /// For existing accounts, only the delta is needed.
110    Delta(AccountDelta),
111}
112
113impl AccountUpdateDetails {
114    /// Returns `true` if the account update details are for private account.
115    pub fn is_private(&self) -> bool {
116        matches!(self, Self::Private)
117    }
118
119    /// Merges the `other` update into this one.
120    ///
121    /// This account update is assumed to come before the other.
122    pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountDeltaError> {
123        let merged_update = match (self, other) {
124            (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
125                AccountUpdateDetails::Private
126            },
127            (AccountUpdateDetails::New(mut account), AccountUpdateDetails::Delta(delta)) => {
128                account.apply_delta(&delta).map_err(|err| {
129                    AccountDeltaError::AccountDeltaApplicationFailed {
130                        account_id: account.id(),
131                        source: err,
132                    }
133                })?;
134
135                AccountUpdateDetails::New(account)
136            },
137            (AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => {
138                delta.merge(new_delta)?;
139                AccountUpdateDetails::Delta(delta)
140            },
141            (left, right) => {
142                return Err(AccountDeltaError::IncompatibleAccountUpdates {
143                    left_update_type: left.as_tag_str(),
144                    right_update_type: right.as_tag_str(),
145                })
146            },
147        };
148
149        Ok(merged_update)
150    }
151
152    /// Returns the tag of the [`AccountUpdateDetails`] as a string for inclusion in error messages.
153    pub(crate) const fn as_tag_str(&self) -> &'static str {
154        match self {
155            AccountUpdateDetails::Private => "private",
156            AccountUpdateDetails::New(_) => "new",
157            AccountUpdateDetails::Delta(_) => "delta",
158        }
159    }
160}
161
162/// Converts an [Account] into an [AccountDelta] for initial delta construction.
163impl From<Account> for AccountDelta {
164    fn from(account: Account) -> Self {
165        let (_id, vault, storage, _code, nonce) = account.into_parts();
166        AccountDelta {
167            storage: storage.into(),
168            vault: (&vault).into(),
169            nonce: Some(nonce),
170        }
171    }
172}
173
174// SERIALIZATION
175// ================================================================================================
176
177impl Serializable for AccountDelta {
178    fn write_into<W: ByteWriter>(&self, target: &mut W) {
179        self.storage.write_into(target);
180        self.vault.write_into(target);
181        self.nonce.write_into(target);
182    }
183
184    fn get_size_hint(&self) -> usize {
185        self.storage.get_size_hint() + self.vault.get_size_hint() + self.nonce.get_size_hint()
186    }
187}
188
189impl Deserializable for AccountDelta {
190    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
191        let storage = AccountStorageDelta::read_from(source)?;
192        let vault = AccountVaultDelta::read_from(source)?;
193        let nonce = <Option<Felt>>::read_from(source)?;
194
195        validate_nonce(nonce, &storage, &vault)
196            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
197
198        Ok(Self { storage, vault, nonce })
199    }
200}
201
202impl Serializable for AccountUpdateDetails {
203    fn write_into<W: ByteWriter>(&self, target: &mut W) {
204        match self {
205            AccountUpdateDetails::Private => {
206                0_u8.write_into(target);
207            },
208            AccountUpdateDetails::New(account) => {
209                1_u8.write_into(target);
210                account.write_into(target);
211            },
212            AccountUpdateDetails::Delta(delta) => {
213                2_u8.write_into(target);
214                delta.write_into(target);
215            },
216        }
217    }
218
219    fn get_size_hint(&self) -> usize {
220        // Size of the serialized enum tag.
221        let u8_size = 0u8.get_size_hint();
222
223        match self {
224            AccountUpdateDetails::Private => u8_size,
225            AccountUpdateDetails::New(account) => u8_size + account.get_size_hint(),
226            AccountUpdateDetails::Delta(account_delta) => u8_size + account_delta.get_size_hint(),
227        }
228    }
229}
230
231impl Deserializable for AccountUpdateDetails {
232    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
233        match u8::read_from(source)? {
234            0 => Ok(Self::Private),
235            1 => Ok(Self::New(Account::read_from(source)?)),
236            2 => Ok(Self::Delta(AccountDelta::read_from(source)?)),
237            v => Err(DeserializationError::InvalidValue(format!(
238                "Unknown variant {v} for AccountDetails"
239            ))),
240        }
241    }
242}
243
244// HELPER FUNCTIONS
245// ================================================================================================
246
247/// Checks if the nonce was updated correctly given the provided storage and vault deltas.
248///
249/// # Errors
250/// Returns an error if storage or vault were updated, but the nonce was either not updated
251/// or set to 0.
252fn validate_nonce(
253    nonce: Option<Felt>,
254    storage: &AccountStorageDelta,
255    vault: &AccountVaultDelta,
256) -> Result<(), AccountDeltaError> {
257    if !storage.is_empty() || !vault.is_empty() {
258        match nonce {
259            Some(nonce) => {
260                if nonce == ZERO {
261                    return Err(AccountDeltaError::InconsistentNonceUpdate(
262                        "zero nonce for a non-empty account delta".to_string(),
263                    ));
264                }
265            },
266            None => {
267                return Err(AccountDeltaError::InconsistentNonceUpdate(
268                    "nonce not updated for non-empty account delta".to_string(),
269                ))
270            },
271        }
272    }
273
274    Ok(())
275}
276
277// TESTS
278// ================================================================================================
279
280#[cfg(test)]
281mod tests {
282
283    use vm_core::{utils::Serializable, Felt, FieldElement};
284
285    use super::{AccountDelta, AccountStorageDelta, AccountVaultDelta};
286    use crate::{
287        account::{
288            delta::AccountUpdateDetails, Account, AccountCode, AccountId, AccountStorage,
289            AccountStorageMode, AccountType, StorageMapDelta,
290        },
291        asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails},
292        testing::account_id::{
293            AccountIdBuilder, ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
294        },
295        ONE, ZERO,
296    };
297
298    #[test]
299    fn account_delta_nonce_validation() {
300        // empty delta
301        let storage_delta = AccountStorageDelta::default();
302        let vault_delta = AccountVaultDelta::default();
303
304        assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_ok());
305        assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok());
306
307        // non-empty delta
308        let storage_delta = AccountStorageDelta::from_iters([1], [], []);
309
310        assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_err());
311        assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ZERO)).is_err());
312        assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok());
313    }
314
315    #[test]
316    fn account_update_details_size_hint() {
317        // AccountDelta
318
319        let storage_delta = AccountStorageDelta::default();
320        let vault_delta = AccountVaultDelta::default();
321        assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
322        assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
323
324        let account_delta = AccountDelta::new(storage_delta, vault_delta, None).unwrap();
325        assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
326
327        let storage_delta = AccountStorageDelta::from_iters(
328            [1],
329            [(2, [ONE, ONE, ONE, ONE]), (3, [ONE, ONE, ZERO, ONE])],
330            [(
331                4,
332                StorageMapDelta::from_iters(
333                    [[ONE, ONE, ONE, ZERO], [ZERO, ONE, ONE, ONE]],
334                    [([ONE, ONE, ONE, ONE], [ONE, ONE, ONE, ONE])],
335                ),
336            )],
337        );
338
339        let non_fungible: Asset = NonFungibleAsset::new(
340            &NonFungibleAssetDetails::new(
341                AccountIdBuilder::new()
342                    .account_type(AccountType::NonFungibleFaucet)
343                    .storage_mode(AccountStorageMode::Public)
344                    .build_with_rng(&mut rand::thread_rng())
345                    .prefix(),
346                vec![6],
347            )
348            .unwrap(),
349        )
350        .unwrap()
351        .into();
352        let fungible_2: Asset = FungibleAsset::new(
353            AccountIdBuilder::new()
354                .account_type(AccountType::FungibleFaucet)
355                .storage_mode(AccountStorageMode::Public)
356                .build_with_rng(&mut rand::thread_rng()),
357            10,
358        )
359        .unwrap()
360        .into();
361        let vault_delta = AccountVaultDelta::from_iters([non_fungible], [fungible_2]);
362
363        assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
364        assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
365
366        let account_delta = AccountDelta::new(storage_delta, vault_delta, Some(ONE)).unwrap();
367        assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
368
369        // Account
370
371        let account_id =
372            AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap();
373
374        let asset_vault = AssetVault::mock();
375        assert_eq!(asset_vault.to_bytes().len(), asset_vault.get_size_hint());
376
377        let account_storage = AccountStorage::mock();
378        assert_eq!(account_storage.to_bytes().len(), account_storage.get_size_hint());
379
380        let account_code = AccountCode::mock();
381        assert_eq!(account_code.to_bytes().len(), account_code.get_size_hint());
382
383        let account =
384            Account::from_parts(account_id, asset_vault, account_storage, account_code, Felt::ZERO);
385        assert_eq!(account.to_bytes().len(), account.get_size_hint());
386
387        // AccountUpdateDetails
388
389        let update_details_private = AccountUpdateDetails::Private;
390        assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
391
392        let update_details_delta = AccountUpdateDetails::Delta(account_delta);
393        assert_eq!(update_details_delta.to_bytes().len(), update_details_delta.get_size_hint());
394
395        let update_details_new = AccountUpdateDetails::New(account);
396        assert_eq!(update_details_new.to_bytes().len(), update_details_new.get_size_hint());
397    }
398}