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}