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