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 an account with this ID is a public account.
121    pub fn is_public(&self) -> bool {
122        self.storage_mode() == AccountStorageMode::Public
123    }
124
125    /// Returns the version of this account ID.
126    pub fn version(&self) -> AccountIdVersion {
127        match self {
128            AccountIdPrefix::V0(_) => AccountIdVersion::Version0,
129        }
130    }
131
132    /// Returns the prefix as a big-endian, hex-encoded string.
133    pub fn to_hex(self) -> String {
134        match self {
135            AccountIdPrefix::V0(id_prefix) => id_prefix.to_hex(),
136        }
137    }
138
139    /// Returns `felt` with the fungible bit set to zero. The version must be passed as the location
140    /// of the fungible bit may depend on the underlying account ID version.
141    pub(crate) fn clear_fungible_bit(version: AccountIdVersion, felt: Felt) -> Felt {
142        match version {
143            AccountIdVersion::Version0 => {
144                // Set the fungible bit to zero by taking the bitwise `and` of the felt with the
145                // inverted is_faucet mask.
146                let clear_fungible_bit_mask = !AccountIdV0::IS_FAUCET_MASK;
147                Felt::try_from(felt.as_int() & clear_fungible_bit_mask)
148                    .expect("felt should still be valid as we cleared a bit and did not set any")
149            },
150        }
151    }
152}
153
154// CONVERSIONS FROM ACCOUNT ID PREFIX
155// ================================================================================================
156
157impl From<AccountIdPrefixV0> for AccountIdPrefix {
158    fn from(id: AccountIdPrefixV0) -> Self {
159        Self::V0(id)
160    }
161}
162
163impl From<AccountIdPrefix> for Felt {
164    fn from(id: AccountIdPrefix) -> Self {
165        match id {
166            AccountIdPrefix::V0(id_prefix) => id_prefix.into(),
167        }
168    }
169}
170
171impl From<AccountIdPrefix> for [u8; 8] {
172    fn from(id: AccountIdPrefix) -> Self {
173        match id {
174            AccountIdPrefix::V0(id_prefix) => id_prefix.into(),
175        }
176    }
177}
178
179impl From<AccountIdPrefix> for u64 {
180    fn from(id: AccountIdPrefix) -> Self {
181        match id {
182            AccountIdPrefix::V0(id_prefix) => id_prefix.into(),
183        }
184    }
185}
186
187// CONVERSIONS TO ACCOUNT ID PREFIX
188// ================================================================================================
189
190impl TryFrom<[u8; 8]> for AccountIdPrefix {
191    type Error = AccountIdError;
192
193    /// Tries to convert a byte array in big-endian order to an [`AccountIdPrefix`].
194    ///
195    /// # Errors
196    ///
197    /// Returns an error if any of the ID constraints are not met. See the [constraints
198    /// documentation](super::AccountId#constraints) for details.
199    fn try_from(value: [u8; 8]) -> Result<Self, Self::Error> {
200        // The least significant byte of the ID prefix contains the metadata.
201        let metadata_byte = value[7];
202        // We only have one supported version for now, so we use the extractor from that version.
203        // If we add more versions in the future, we may need to generalize this.
204        let version = v0::extract_version(metadata_byte as u64)?;
205
206        match version {
207            AccountIdVersion::Version0 => AccountIdPrefixV0::try_from(value).map(Self::V0),
208        }
209    }
210}
211
212impl TryFrom<u64> for AccountIdPrefix {
213    type Error = AccountIdError;
214
215    /// Tries to convert a `u64` into an [`AccountIdPrefix`].
216    ///
217    /// # Errors
218    ///
219    /// Returns an error if any of the ID constraints are not met. See the [constraints
220    /// documentation](super::AccountId#constraints) for details.
221    fn try_from(value: u64) -> Result<Self, Self::Error> {
222        let element = Felt::try_from(value.to_le_bytes().as_slice())
223            .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
224        Self::new(element)
225    }
226}
227
228impl TryFrom<Felt> for AccountIdPrefix {
229    type Error = AccountIdError;
230
231    /// Returns an [`AccountIdPrefix`] instantiated with the provided field element.
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(element: Felt) -> Result<Self, Self::Error> {
238        Self::new(element)
239    }
240}
241
242// COMMON TRAIT IMPLS
243// ================================================================================================
244
245impl PartialOrd for AccountIdPrefix {
246    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
247        Some(self.cmp(other))
248    }
249}
250
251impl Ord for AccountIdPrefix {
252    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
253        u64::from(*self).cmp(&u64::from(*other))
254    }
255}
256
257impl fmt::Display for AccountIdPrefix {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        write!(f, "{}", self.to_hex())
260    }
261}
262
263// SERIALIZATION
264// ================================================================================================
265
266impl Serializable for AccountIdPrefix {
267    fn write_into<W: ByteWriter>(&self, target: &mut W) {
268        match self {
269            AccountIdPrefix::V0(id_prefix) => id_prefix.write_into(target),
270        }
271    }
272
273    fn get_size_hint(&self) -> usize {
274        match self {
275            AccountIdPrefix::V0(id_prefix) => id_prefix.get_size_hint(),
276        }
277    }
278}
279
280impl Deserializable for AccountIdPrefix {
281    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
282        <[u8; 8]>::read_from(source)?
283            .try_into()
284            .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
285    }
286}
287
288// TESTS
289// ================================================================================================
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use crate::account::AccountIdV0;
295
296    #[test]
297    fn account_id_prefix_construction() {
298        // Use the highest possible input to check if the constructed id is a valid Felt in that
299        // scenario.
300        // Use the lowest possible input to check whether the constructor produces valid IDs with
301        // all-zeroes input.
302        for input in [[0xff; 15], [0; 15]] {
303            for account_type in [
304                AccountType::FungibleFaucet,
305                AccountType::NonFungibleFaucet,
306                AccountType::RegularAccountImmutableCode,
307                AccountType::RegularAccountUpdatableCode,
308            ] {
309                for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
310                    let id = AccountIdV0::dummy(input, account_type, storage_mode);
311                    let prefix = id.prefix();
312                    assert_eq!(prefix.account_type(), account_type);
313                    assert_eq!(prefix.storage_mode(), storage_mode);
314                    assert_eq!(prefix.version(), AccountIdVersion::Version0);
315
316                    // Do a serialization roundtrip to ensure validity.
317                    let serialized_prefix = prefix.to_bytes();
318                    AccountIdPrefix::read_from_bytes(&serialized_prefix).unwrap();
319                    assert_eq!(serialized_prefix.len(), AccountIdPrefix::SERIALIZED_SIZE);
320                }
321            }
322        }
323    }
324}