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}