miden_objects/account/account_id/
id_prefix.rs

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