Skip to main content

miden_protocol/account/patch/
update_details.rs

1use crate::account::AccountPatch;
2use crate::errors::AccountPatchError;
3use crate::utils::serde::{
4    ByteReader,
5    ByteWriter,
6    Deserializable,
7    DeserializationError,
8    Serializable,
9};
10
11// ACCOUNT UPDATE DETAILS
12// ================================================================================================
13
14/// Describes the update of an account at any aggregation level: as the result of a single
15/// transaction, of all transactions in a batch, or of all batches in a block. The representation is
16/// the same in all cases, which lets transaction-, batch-, and block-level updates merge into one
17/// another.
18///
19/// For a public account, the update is represented as an [`AccountPatch`] describing the new
20/// absolute state of the changed components of the account. For a private account, the actual state
21/// change is not publicly visible and only the state commitments are; this is captured by the
22/// [`AccountUpdateDetails::Private`] variant.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum AccountUpdateDetails {
25    /// The state update of a private account is not publicly accessible.
26    Private,
27
28    /// The state update of a public account, represented as an [`AccountPatch`] describing the new
29    /// absolute state of the changed components of the account.
30    Public(AccountPatch),
31}
32
33impl AccountUpdateDetails {
34    const PRIVATE_TAG: u8 = 0;
35    const PUBLIC_TAG: u8 = 1;
36
37    /// Returns `true` if the account update details are for a private account, `false` otherwise.
38    pub fn is_private(&self) -> bool {
39        matches!(self, Self::Private)
40    }
41
42    /// Returns `true` if the account update details are for a public account, `false` otherwise.
43    pub fn is_public(&self) -> bool {
44        matches!(self, Self::Public(_))
45    }
46
47    /// Merges the `other` update into this one.
48    ///
49    /// This account update (`self`) must come before `other`, i.e. `self.nonce + 1` must be equal
50    /// to `other.nonce`.
51    pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountPatchError> {
52        let merged_update = match (self, other) {
53            (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
54                AccountUpdateDetails::Private
55            },
56            (AccountUpdateDetails::Public(mut patch), AccountUpdateDetails::Public(new_patch)) => {
57                patch.merge(new_patch)?;
58                AccountUpdateDetails::Public(patch)
59            },
60            (left, right) => {
61                return Err(AccountPatchError::IncompatibleAccountUpdates {
62                    left_update_type: left.as_tag_str(),
63                    right_update_type: right.as_tag_str(),
64                });
65            },
66        };
67
68        Ok(merged_update)
69    }
70
71    /// Returns the tag of the [`AccountUpdateDetails`] as a string for inclusion in error messages.
72    pub(crate) const fn as_tag_str(&self) -> &'static str {
73        match self {
74            AccountUpdateDetails::Private => "private",
75            AccountUpdateDetails::Public(_) => "public",
76        }
77    }
78}
79
80// SERIALIZATION
81// ================================================================================================
82
83impl Serializable for AccountUpdateDetails {
84    fn write_into<W: ByteWriter>(&self, target: &mut W) {
85        match self {
86            AccountUpdateDetails::Private => {
87                Self::PRIVATE_TAG.write_into(target);
88            },
89            AccountUpdateDetails::Public(public) => {
90                Self::PUBLIC_TAG.write_into(target);
91                public.write_into(target);
92            },
93        }
94    }
95
96    fn get_size_hint(&self) -> usize {
97        // Size of the serialized enum tag.
98        let u8_size = 0u8.get_size_hint();
99
100        match self {
101            AccountUpdateDetails::Private => u8_size,
102            AccountUpdateDetails::Public(public) => u8_size + public.get_size_hint(),
103        }
104    }
105}
106
107impl Deserializable for AccountUpdateDetails {
108    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
109        match u8::read_from(source)? {
110            Self::PRIVATE_TAG => Ok(Self::Private),
111            Self::PUBLIC_TAG => Ok(Self::Public(AccountPatch::read_from(source)?)),
112            variant => Err(DeserializationError::InvalidValue(format!(
113                "Unknown variant {variant} for AccountUpdateDetails"
114            ))),
115        }
116    }
117}
118
119// TESTS
120// ================================================================================================
121
122#[cfg(test)]
123mod tests {
124    use super::AccountUpdateDetails;
125    use crate::account::{
126        AccountCode,
127        AccountId,
128        AccountPatch,
129        AccountStoragePatch,
130        AccountVaultPatch,
131        StorageMapKey,
132        StorageSlotName,
133    };
134    use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
135    use crate::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER;
136    use crate::utils::serde::Serializable;
137    use crate::{ONE, Word};
138
139    #[test]
140    fn account_update_details_size_hint() -> anyhow::Result<()> {
141        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
142
143        // A full state patch may only create slots, so build it with create ops.
144        let storage_patch = AccountStoragePatch::builder()
145            .create_value(StorageSlotName::mock(2), Word::from([1, 1, 1, 1u32]))
146            .create_value(StorageSlotName::mock(3), Word::from([1, 1, 0, 1u32]))
147            .create_map(
148                StorageSlotName::mock(4),
149                [(StorageMapKey::from_array([1, 1, 1, 1]), Word::from([1, 1, 1, 1u32]))],
150            )
151            .build();
152
153        let non_fungible: Asset = NonFungibleAsset::mock(&[6]);
154        let fungible: Asset = FungibleAsset::mock(42);
155        let vault_patch = AccountVaultPatch::with_assets([non_fungible, fungible]);
156
157        let account_patch = AccountPatch::new(
158            account_id,
159            storage_patch,
160            vault_patch,
161            Some(AccountCode::mock()),
162            Some(ONE),
163        )?;
164
165        let update_details_private = AccountUpdateDetails::Private;
166        assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
167
168        let update_details_patch = AccountUpdateDetails::Public(account_patch);
169        assert_eq!(update_details_patch.to_bytes().len(), update_details_patch.get_size_hint());
170
171        // Verify the `Public` flavor differs from `Private` in size, just to confirm both branches
172        // are exercised.
173        assert!(update_details_patch.get_size_hint() > update_details_private.get_size_hint());
174
175        Ok(())
176    }
177}