miden_objects/account/
partial.rs

1use alloc::string::ToString;
2
3use miden_core::utils::{Deserializable, Serializable};
4use miden_core::{Felt, ZERO};
5
6use super::{Account, AccountCode, AccountId, PartialStorage};
7use crate::account::{hash_account, validate_account_seed};
8use crate::asset::PartialVault;
9use crate::utils::serde::DeserializationError;
10use crate::{AccountError, Word};
11
12/// A partial representation of an account.
13///
14/// A partial account is used as inputs to the transaction kernel and contains only the essential
15/// data needed for verification and transaction processing without requiring the full account
16/// state.
17///
18/// For new accounts, the partial storage must be the full initial account storage.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct PartialAccount {
21    /// The ID for the partial account
22    id: AccountId,
23    /// Partial representation of the account's vault, containing the vault root and necessary
24    /// proof information for asset verification
25    partial_vault: PartialVault,
26    /// Partial representation of the account's storage, containing the storage commitment and
27    /// proofs for specific storage slots that need to be accessed
28    partial_storage: PartialStorage,
29    /// Account code
30    code: AccountCode,
31    /// The current transaction nonce of the account
32    nonce: Felt,
33    /// The seed of the account ID, if any.
34    seed: Option<Word>,
35}
36
37impl PartialAccount {
38    // CONSTRUCTORS
39    // --------------------------------------------------------------------------------------------
40
41    /// Creates a new [`PartialAccount`] with the provided account parts and seed.
42    ///
43    /// # Errors
44    ///
45    /// Returns an error if:
46    /// - an account seed is provided but the account's nonce indicates the account already exists.
47    /// - an account seed is not provided but the account's nonce indicates the account is new.
48    /// - an account seed is provided but the account ID derived from it is invalid or does not
49    ///   match the provided ID.
50    pub fn new(
51        id: AccountId,
52        nonce: Felt,
53        code: AccountCode,
54        partial_storage: PartialStorage,
55        partial_vault: PartialVault,
56        seed: Option<Word>,
57    ) -> Result<Self, AccountError> {
58        validate_account_seed(id, code.commitment(), partial_storage.commitment(), seed, nonce)?;
59
60        let account = Self {
61            id,
62            nonce,
63            code,
64            partial_storage,
65            partial_vault,
66            seed,
67        };
68
69        Ok(account)
70    }
71
72    // ACCESSORS
73    // --------------------------------------------------------------------------------------------
74
75    /// Returns the account's unique identifier.
76    pub fn id(&self) -> AccountId {
77        self.id
78    }
79
80    /// Returns the account's current nonce value.
81    pub fn nonce(&self) -> Felt {
82        self.nonce
83    }
84
85    /// Returns a reference to the account code.
86    pub fn code(&self) -> &AccountCode {
87        &self.code
88    }
89
90    /// Returns a reference to the partial storage representation of the account.
91    pub fn storage(&self) -> &PartialStorage {
92        &self.partial_storage
93    }
94
95    /// Returns a reference to the partial vault representation of the account.
96    pub fn vault(&self) -> &PartialVault {
97        &self.partial_vault
98    }
99
100    /// Returns the seed of the account's ID if the account is new.
101    ///
102    /// That is, if [`PartialAccount::is_new`] returns `true`, the seed will be `Some`.
103    pub fn seed(&self) -> Option<Word> {
104        self.seed
105    }
106
107    /// Returns `true` if the account is new, `false` otherwise.
108    ///
109    /// An account is considered new if the account's nonce is zero and it hasn't been registered on
110    /// chain yet.
111    pub fn is_new(&self) -> bool {
112        self.nonce == ZERO
113    }
114
115    /// Returns the commitment of this account.
116    ///
117    /// The commitment of an account is computed as:
118    ///
119    /// ```text
120    /// hash(id, nonce, vault_root, storage_commitment, code_commitment).
121    /// ```
122    pub fn commitment(&self) -> Word {
123        hash_account(
124            self.id,
125            self.nonce,
126            self.vault().root(),
127            self.storage().commitment(),
128            self.code().commitment(),
129        )
130    }
131
132    /// Returns the commitment of this account as used for the initial account state commitment in
133    /// transaction proofs.
134    ///
135    /// For existing accounts, this is exactly the same as [Account::commitment()], however, for new
136    /// accounts this value is set to [`Word::empty`]. This is because when a transaction is
137    /// executed against a new account, public input for the initial account state is set to
138    /// [`Word::empty`] to distinguish new accounts from existing accounts. The actual
139    /// commitment of the initial account state (and the initial state itself), are provided to
140    /// the VM via the advice provider.
141    pub fn initial_commitment(&self) -> Word {
142        if self.is_new() {
143            Word::empty()
144        } else {
145            self.commitment()
146        }
147    }
148
149    /// Returns `true` if the full state of the account is public on chain, and `false` otherwise.
150    pub fn has_public_state(&self) -> bool {
151        self.id.has_public_state()
152    }
153
154    /// Consumes self and returns the underlying parts of the partial account.
155    pub fn into_parts(
156        self,
157    ) -> (AccountId, PartialVault, PartialStorage, AccountCode, Felt, Option<Word>) {
158        (
159            self.id,
160            self.partial_vault,
161            self.partial_storage,
162            self.code,
163            self.nonce,
164            self.seed,
165        )
166    }
167}
168
169impl From<&Account> for PartialAccount {
170    /// Constructs a [`PartialAccount`] from the provided account.
171    ///
172    /// The behavior is different whether the [`Account::is_new`] or not:
173    /// - For new accounts, the storage is tracked in full. This is because transactions that create
174    ///   accounts need the full state.
175    /// - For existing accounts, the storage is tracked minimally, i.e. the minimal necessary data
176    ///   is included.
177    ///
178    /// Because new accounts always have empty vaults, in both cases, the asset vault is a minimal
179    /// representation.
180    ///
181    /// For precise control over how an account is converted to a partial account, use
182    /// [`PartialAccount::new`].
183    fn from(account: &Account) -> Self {
184        let partial_storage = if account.is_new() {
185            // This is somewhat expensive, but it allows us to do this conversion from &Account and
186            // it penalizes only the rare case (new accounts).
187            PartialStorage::new_full(account.storage.clone())
188        } else {
189            PartialStorage::new_minimal(account.storage())
190        };
191
192        Self::new(
193            account.id(),
194            account.nonce(),
195            account.code().clone(),
196            partial_storage,
197            PartialVault::new_minimal(account.vault()),
198            account.seed(),
199        )
200        .expect("account should ensure that seed is valid for account")
201    }
202}
203
204impl Serializable for PartialAccount {
205    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
206        target.write(self.id);
207        target.write(self.nonce);
208        target.write(&self.code);
209        target.write(&self.partial_storage);
210        target.write(&self.partial_vault);
211        target.write(self.seed);
212    }
213}
214
215impl Deserializable for PartialAccount {
216    fn read_from<R: miden_core::utils::ByteReader>(
217        source: &mut R,
218    ) -> Result<Self, miden_processor::DeserializationError> {
219        let account_id = source.read()?;
220        let nonce = source.read()?;
221        let account_code = source.read()?;
222        let partial_storage = source.read()?;
223        let partial_vault = source.read()?;
224        let seed: Option<Word> = source.read()?;
225
226        PartialAccount::new(account_id, nonce, account_code, partial_storage, partial_vault, seed)
227            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
228    }
229}