miden_objects/account/account_id/
id_prefix.rs

1use alloc::string::{String, ToString};
2use core::fmt;
3
4use super::v0;
5use crate::{
6    Felt,
7    account::{
8        AccountIdV0, AccountIdVersion, AccountStorageMode, AccountType,
9        account_id::AccountIdPrefixV0,
10    },
11    errors::AccountIdError,
12    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
13};
14
15// ACCOUNT ID PREFIX
16// ================================================================================================
17
18/// The prefix of an [`AccountId`][id], i.e. its first field element.
19///
20/// See the [`AccountId`][id] documentation for details.
21///
22/// The serialization formats of [`AccountIdPrefix`] and [`AccountId`][id] are compatible. In
23/// particular, a prefix can be deserialized from the serialized bytes of a full id.
24///
25/// [id]: crate::account::AccountId
26#[derive(Debug, Copy, Clone, Eq, PartialEq)]
27pub enum AccountIdPrefix {
28    V0(AccountIdPrefixV0),
29}
30
31impl AccountIdPrefix {
32    // CONSTANTS
33    // --------------------------------------------------------------------------------------------
34
35    /// The serialized size of an [`AccountIdPrefix`] in bytes.
36    pub const SERIALIZED_SIZE: usize = 8;
37
38    // CONSTRUCTORS
39    // --------------------------------------------------------------------------------------------
40
41    /// Constructs a new [`AccountIdPrefix`] from the given `prefix` without checking its
42    /// validity.
43    ///
44    /// # Warning
45    ///
46    /// Validity of the ID prefix must be ensured by the caller. An invalid ID may lead to panics.
47    ///
48    /// # Panics
49    ///
50    /// Panics if the prefix does not contain a known account ID version.
51    ///
52    /// If debug_assertions are enabled (e.g. in debug mode), this function panics if the given
53    /// felt is invalid according to the constraints in the
54    /// [`AccountId`](crate::account::AccountId) documentation.
55    pub fn new_unchecked(prefix: Felt) -> Self {
56        // The prefix contains the metadata.
57        // If we add more versions in the future, we may need to generalize this.
58        match v0::extract_version(prefix.as_int())
59            .expect("prefix should contain a valid account ID version")
60        {
61            AccountIdVersion::Version0 => Self::V0(AccountIdPrefixV0::new_unchecked(prefix)),
62        }
63    }
64
65    /// Constructs a new [`AccountIdPrefix`] from the given `prefix` and checks its validity.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if any of the ID constraints are not met. See the [constraints
70    /// documentation](super::AccountId#constraints) for details.
71    pub fn new(prefix: Felt) -> Result<Self, AccountIdError> {
72        // The prefix contains the metadata.
73        // If we add more versions in the future, we may need to generalize this.
74        match v0::extract_version(prefix.as_int())? {
75            AccountIdVersion::Version0 => AccountIdPrefixV0::new(prefix).map(Self::V0),
76        }
77    }
78
79    // PUBLIC ACCESSORS
80    // --------------------------------------------------------------------------------------------
81
82    /// Returns the [`Felt`] that represents this prefix.
83    pub const fn as_felt(&self) -> Felt {
84        match self {
85            AccountIdPrefix::V0(id_prefix) => id_prefix.as_felt(),
86        }
87    }
88
89    /// Returns the prefix as a [`u64`].
90    pub const fn as_u64(&self) -> u64 {
91        match self {
92            AccountIdPrefix::V0(id_prefix) => id_prefix.as_u64(),
93        }
94    }
95
96    /// Returns the type of this account ID.
97    pub const fn account_type(&self) -> AccountType {
98        match self {
99            AccountIdPrefix::V0(id_prefix) => id_prefix.account_type(),
100        }
101    }
102
103    /// Returns true if an account with this ID is a faucet (can issue assets).
104    pub fn is_faucet(&self) -> bool {
105        self.account_type().is_faucet()
106    }
107
108    /// Returns true if an account with this ID is a regular account.
109    pub fn is_regular_account(&self) -> bool {
110        self.account_type().is_regular_account()
111    }
112
113    /// Returns the storage mode of this account ID.
114    pub fn storage_mode(&self) -> AccountStorageMode {
115        match self {
116            AccountIdPrefix::V0(id_prefix) => id_prefix.storage_mode(),
117        }
118    }
119
120    /// Returns `true` if the full state of the account is on chain, i.e. if the modes are
121    /// [`AccountStorageMode::Public`] or [`AccountStorageMode::Network`], `false` otherwise.
122    pub fn is_onchain(&self) -> bool {
123        self.storage_mode().is_onchain()
124    }
125
126    /// Returns `true` if the storage mode is [`AccountStorageMode::Public`], `false` otherwise.
127    pub fn is_public(&self) -> bool {
128        self.storage_mode().is_public()
129    }
130
131    /// Returns `true` if the storage mode is [`AccountStorageMode::Network`], `false` otherwise.
132    pub fn is_network(&self) -> bool {
133        self.storage_mode().is_network()
134    }
135
136    /// Returns `true` if self is a private account, `false` otherwise.
137    pub fn is_private(&self) -> bool {
138        self.storage_mode().is_private()
139    }
140
141    /// Returns the version of this account ID.
142    pub fn version(&self) -> AccountIdVersion {
143        match self {
144            AccountIdPrefix::V0(_) => AccountIdVersion::Version0,
145        }
146    }
147
148    /// Returns the prefix as a big-endian, hex-encoded string.
149    pub fn to_hex(self) -> String {
150        match self {
151            AccountIdPrefix::V0(id_prefix) => id_prefix.to_hex(),
152        }
153    }
154
155    /// Returns `felt` with the fungible bit set to zero. The version must be passed as the location
156    /// of the fungible bit may depend on the underlying account ID version.
157    pub(crate) fn clear_fungible_bit(version: AccountIdVersion, felt: Felt) -> Felt {
158        match version {
159            AccountIdVersion::Version0 => {
160                // Set the fungible bit to zero by taking the bitwise `and` of the felt with the
161                // inverted is_faucet mask.
162                let clear_fungible_bit_mask = !AccountIdV0::IS_FAUCET_MASK;
163                Felt::try_from(felt.as_int() & clear_fungible_bit_mask)
164                    .expect("felt should still be valid as we cleared a bit and did not set any")
165            },
166        }
167    }
168}
169
170// CONVERSIONS FROM ACCOUNT ID PREFIX
171// ================================================================================================
172
173impl From<AccountIdPrefixV0> for AccountIdPrefix {
174    fn from(id: AccountIdPrefixV0) -> Self {
175        Self::V0(id)
176    }
177}
178
179impl From<AccountIdPrefix> for Felt {
180    fn from(id: AccountIdPrefix) -> Self {
181        match id {
182            AccountIdPrefix::V0(id_prefix) => id_prefix.into(),
183        }
184    }
185}
186
187impl From<AccountIdPrefix> for [u8; 8] {
188    fn from(id: AccountIdPrefix) -> Self {
189        match id {
190            AccountIdPrefix::V0(id_prefix) => id_prefix.into(),
191        }
192    }
193}
194
195impl From<AccountIdPrefix> for u64 {
196    fn from(id: AccountIdPrefix) -> Self {
197        match id {
198            AccountIdPrefix::V0(id_prefix) => id_prefix.into(),
199        }
200    }
201}
202
203// CONVERSIONS TO ACCOUNT ID PREFIX
204// ================================================================================================
205
206impl TryFrom<[u8; 8]> for AccountIdPrefix {
207    type Error = AccountIdError;
208
209    /// Tries to convert a byte array in big-endian order to an [`AccountIdPrefix`].
210    ///
211    /// # Errors
212    ///
213    /// Returns an error if any of the ID constraints are not met. See the [constraints
214    /// documentation](super::AccountId#constraints) for details.
215    fn try_from(value: [u8; 8]) -> Result<Self, Self::Error> {
216        // The least significant byte of the ID prefix contains the metadata.
217        let metadata_byte = value[7];
218        // We only have one supported version for now, so we use the extractor from that version.
219        // If we add more versions in the future, we may need to generalize this.
220        let version = v0::extract_version(metadata_byte as u64)?;
221
222        match version {
223            AccountIdVersion::Version0 => AccountIdPrefixV0::try_from(value).map(Self::V0),
224        }
225    }
226}
227
228impl TryFrom<u64> for AccountIdPrefix {
229    type Error = AccountIdError;
230
231    /// Tries to convert a `u64` into an [`AccountIdPrefix`].
232    ///
233    /// # Errors
234    ///
235    /// Returns an error if any of the ID constraints are not met. See the [constraints
236    /// documentation](super::AccountId#constraints) for details.
237    fn try_from(value: u64) -> Result<Self, Self::Error> {
238        let element = Felt::try_from(value.to_le_bytes().as_slice())
239            .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
240        Self::new(element)
241    }
242}
243
244impl TryFrom<Felt> for AccountIdPrefix {
245    type Error = AccountIdError;
246
247    /// Returns an [`AccountIdPrefix`] instantiated with the provided field element.
248    ///
249    /// # Errors
250    ///
251    /// Returns an error if any of the ID constraints are not met. See the [constraints
252    /// documentation](super::AccountId#constraints) for details.
253    fn try_from(element: Felt) -> Result<Self, Self::Error> {
254        Self::new(element)
255    }
256}
257
258// COMMON TRAIT IMPLS
259// ================================================================================================
260
261impl PartialOrd for AccountIdPrefix {
262    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
263        Some(self.cmp(other))
264    }
265}
266
267impl Ord for AccountIdPrefix {
268    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
269        u64::from(*self).cmp(&u64::from(*other))
270    }
271}
272
273impl fmt::Display for AccountIdPrefix {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        write!(f, "{}", self.to_hex())
276    }
277}
278
279// SERIALIZATION
280// ================================================================================================
281
282impl Serializable for AccountIdPrefix {
283    fn write_into<W: ByteWriter>(&self, target: &mut W) {
284        match self {
285            AccountIdPrefix::V0(id_prefix) => id_prefix.write_into(target),
286        }
287    }
288
289    fn get_size_hint(&self) -> usize {
290        match self {
291            AccountIdPrefix::V0(id_prefix) => id_prefix.get_size_hint(),
292        }
293    }
294}
295
296impl Deserializable for AccountIdPrefix {
297    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
298        <[u8; 8]>::read_from(source)?
299            .try_into()
300            .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
301    }
302}
303
304// TESTS
305// ================================================================================================
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::account::AccountIdV0;
311
312    #[test]
313    fn account_id_prefix_construction() {
314        // Use the highest possible input to check if the constructed id is a valid Felt in that
315        // scenario.
316        // Use the lowest possible input to check whether the constructor produces valid IDs with
317        // all-zeroes input.
318        for input in [[0xff; 15], [0; 15]] {
319            for account_type in [
320                AccountType::FungibleFaucet,
321                AccountType::NonFungibleFaucet,
322                AccountType::RegularAccountImmutableCode,
323                AccountType::RegularAccountUpdatableCode,
324            ] {
325                for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
326                    let id = AccountIdV0::dummy(input, account_type, storage_mode);
327                    let prefix = id.prefix();
328                    assert_eq!(prefix.account_type(), account_type);
329                    assert_eq!(prefix.storage_mode(), storage_mode);
330                    assert_eq!(prefix.version(), AccountIdVersion::Version0);
331
332                    // Do a serialization roundtrip to ensure validity.
333                    let serialized_prefix = prefix.to_bytes();
334                    AccountIdPrefix::read_from_bytes(&serialized_prefix).unwrap();
335                    assert_eq!(serialized_prefix.len(), AccountIdPrefix::SERIALIZED_SIZE);
336                }
337            }
338        }
339    }
340}