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