miden_protocol/account/storage/slot/
slot_name.rs1use alloc::string::{String, ToString};
2use alloc::sync::Arc;
3use core::fmt::Display;
4use core::str::FromStr;
5
6use crate::account::storage::slot::StorageSlotId;
7use crate::errors::StorageSlotNameError;
8use crate::utils::serde::{ByteWriter, Deserializable, DeserializationError, Serializable};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub struct StorageSlotName {
39 name: Arc<str>,
40 id: StorageSlotId,
41}
42
43impl StorageSlotName {
44 pub(crate) const MIN_NUM_COMPONENTS: usize = 2;
49
50 pub(crate) const MAX_LENGTH: usize = u8::MAX as usize;
52
53 pub fn new(name: impl Into<Arc<str>>) -> Result<Self, StorageSlotNameError> {
63 let name: Arc<str> = name.into();
64 Self::validate(&name)?;
65 let id = StorageSlotId::from_str(&name);
66 Ok(Self { name, id })
67 }
68
69 pub fn as_str(&self) -> &str {
74 &self.name
75 }
76
77 #[allow(clippy::len_without_is_empty)]
81 pub fn len(&self) -> u8 {
82 debug_assert!(self.name.len() <= Self::MAX_LENGTH);
84 self.name.len() as u8
85 }
86
87 pub fn id(&self) -> StorageSlotId {
89 self.id
90 }
91
92 const fn validate(name: &str) -> Result<(), StorageSlotNameError> {
111 let bytes = name.as_bytes();
112 let mut idx = 0;
113 let mut num_components = 0;
114
115 if bytes.is_empty() {
116 return Err(StorageSlotNameError::TooShort);
117 }
118
119 if bytes.len() > Self::MAX_LENGTH {
120 return Err(StorageSlotNameError::TooLong);
121 }
122
123 if bytes[0] == b':' {
126 return Err(StorageSlotNameError::UnexpectedColon);
127 } else if bytes[0] == b'_' {
128 return Err(StorageSlotNameError::UnexpectedUnderscore);
129 }
130
131 while idx < bytes.len() {
132 let byte = bytes[idx];
133
134 let is_colon = byte == b':';
135
136 if is_colon {
137 if (idx + 1) < bytes.len() {
140 if bytes[idx + 1] != b':' {
141 return Err(StorageSlotNameError::UnexpectedColon);
142 }
143 } else {
144 return Err(StorageSlotNameError::UnexpectedColon);
145 }
146
147 if (idx + 2) < bytes.len() {
150 if bytes[idx + 2] == b':' {
151 return Err(StorageSlotNameError::UnexpectedColon);
152 } else if bytes[idx + 2] == b'_' {
153 return Err(StorageSlotNameError::UnexpectedUnderscore);
154 }
155 } else {
156 return Err(StorageSlotNameError::UnexpectedColon);
157 }
158
159 idx += 2;
161
162 num_components += 1;
164 } else if Self::is_valid_char(byte) {
165 idx += 1;
166 } else {
167 return Err(StorageSlotNameError::InvalidCharacter);
168 }
169 }
170
171 num_components += 1;
173
174 if num_components < Self::MIN_NUM_COMPONENTS {
175 return Err(StorageSlotNameError::TooShort);
176 }
177
178 Ok(())
179 }
180
181 const fn is_valid_char(byte: u8) -> bool {
183 byte.is_ascii_alphanumeric() || byte == b'_'
184 }
185}
186
187impl Ord for StorageSlotName {
188 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
189 self.id().cmp(&other.id())
190 }
191}
192
193impl PartialOrd for StorageSlotName {
194 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
195 Some(self.cmp(other))
196 }
197}
198
199impl Display for StorageSlotName {
200 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
201 f.write_str(self.as_str())
202 }
203}
204
205impl FromStr for StorageSlotName {
206 type Err = StorageSlotNameError;
207
208 fn from_str(string: &str) -> Result<Self, Self::Err> {
209 StorageSlotName::new(string)
210 }
211}
212
213impl From<StorageSlotName> for String {
214 fn from(slot_name: StorageSlotName) -> Self {
215 slot_name.name.to_string()
216 }
217}
218
219impl Serializable for StorageSlotName {
220 fn write_into<W: ByteWriter>(&self, target: &mut W) {
221 target.write_u8(self.len());
222 target.write_many(self.as_str().as_bytes())
223 }
224
225 fn get_size_hint(&self) -> usize {
226 1 + self.as_str().len()
228 }
229}
230
231impl Deserializable for StorageSlotName {
232 fn read_from<R: miden_core::utils::ByteReader>(
233 source: &mut R,
234 ) -> Result<Self, DeserializationError> {
235 let len = source.read_u8()?;
236 let name = source.read_many(len as usize)?;
237 String::from_utf8(name)
238 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
239 .and_then(|name| {
240 Self::new(name).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
241 })
242 }
243}
244
245#[cfg(test)]
249mod tests {
250 use std::borrow::ToOwned;
251
252 use assert_matches::assert_matches;
253
254 use super::*;
255
256 const FULL_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
258
259 #[test]
263 fn slot_name_fails_on_invalid_colon_placement() {
264 assert_matches!(
266 StorageSlotName::new(":").unwrap_err(),
267 StorageSlotNameError::UnexpectedColon
268 );
269 assert_matches!(
270 StorageSlotName::new("0::1:").unwrap_err(),
271 StorageSlotNameError::UnexpectedColon
272 );
273 assert_matches!(
274 StorageSlotName::new(":0::1").unwrap_err(),
275 StorageSlotNameError::UnexpectedColon
276 );
277 assert_matches!(
278 StorageSlotName::new("0::1:2").unwrap_err(),
279 StorageSlotNameError::UnexpectedColon
280 );
281
282 assert_matches!(
284 StorageSlotName::new("::").unwrap_err(),
285 StorageSlotNameError::UnexpectedColon
286 );
287 assert_matches!(
288 StorageSlotName::new("1::2::").unwrap_err(),
289 StorageSlotNameError::UnexpectedColon
290 );
291 assert_matches!(
292 StorageSlotName::new("::1::2").unwrap_err(),
293 StorageSlotNameError::UnexpectedColon
294 );
295
296 assert_matches!(
298 StorageSlotName::new(":::").unwrap_err(),
299 StorageSlotNameError::UnexpectedColon
300 );
301 assert_matches!(
302 StorageSlotName::new("1::2:::").unwrap_err(),
303 StorageSlotNameError::UnexpectedColon
304 );
305 assert_matches!(
306 StorageSlotName::new(":::1::2").unwrap_err(),
307 StorageSlotNameError::UnexpectedColon
308 );
309 assert_matches!(
310 StorageSlotName::new("1::2:::3").unwrap_err(),
311 StorageSlotNameError::UnexpectedColon
312 );
313 }
314
315 #[test]
316 fn slot_name_fails_on_invalid_underscore_placement() {
317 assert_matches!(
318 StorageSlotName::new("_one::two").unwrap_err(),
319 StorageSlotNameError::UnexpectedUnderscore
320 );
321 assert_matches!(
322 StorageSlotName::new("one::_two").unwrap_err(),
323 StorageSlotNameError::UnexpectedUnderscore
324 );
325 }
326
327 #[test]
331 fn slot_name_fails_on_empty_string() {
332 assert_matches!(StorageSlotName::new("").unwrap_err(), StorageSlotNameError::TooShort);
333 }
334
335 #[test]
336 fn slot_name_fails_on_single_component() {
337 assert_matches!(
338 StorageSlotName::new("single_component").unwrap_err(),
339 StorageSlotNameError::TooShort
340 );
341 }
342
343 #[test]
344 fn slot_name_fails_on_string_whose_length_exceeds_max_length() {
345 let mut string = get_max_length_slot_name();
346 string.push('a');
347 assert_matches!(StorageSlotName::new(string).unwrap_err(), StorageSlotNameError::TooLong);
348 }
349
350 #[test]
354 fn slot_name_allows_ascii_alphanumeric_and_underscore() -> anyhow::Result<()> {
355 let name = format!("{FULL_ALPHABET}::second");
356 let slot_name = StorageSlotName::new(name.clone())?;
357 assert_eq!(slot_name.as_str(), name);
358
359 Ok(())
360 }
361
362 #[test]
363 fn slot_name_fails_on_invalid_character() {
364 assert_matches!(
365 StorageSlotName::new("na#me::second").unwrap_err(),
366 StorageSlotNameError::InvalidCharacter
367 );
368 assert_matches!(
369 StorageSlotName::new("first_entry::secönd").unwrap_err(),
370 StorageSlotNameError::InvalidCharacter
371 );
372 assert_matches!(
373 StorageSlotName::new("first::sec::th!rd").unwrap_err(),
374 StorageSlotNameError::InvalidCharacter
375 );
376 }
377
378 #[test]
382 fn slot_name_with_min_components_is_valid() -> anyhow::Result<()> {
383 StorageSlotName::new("miden::component")?;
384 Ok(())
385 }
386
387 #[test]
388 fn slot_name_with_many_components_is_valid() -> anyhow::Result<()> {
389 StorageSlotName::new("miden::faucet0::fungible_1::b4sic::metadata")?;
390 Ok(())
391 }
392
393 #[test]
394 fn slot_name_with_max_length_is_valid() -> anyhow::Result<()> {
395 StorageSlotName::new(get_max_length_slot_name())?;
396 Ok(())
397 }
398
399 #[test]
403 fn serde_slot_name() -> anyhow::Result<()> {
404 let slot_name = StorageSlotName::new("miden::faucet0::fungible_1::b4sic::metadata")?;
405 assert_eq!(slot_name, StorageSlotName::read_from_bytes(&slot_name.to_bytes())?);
406 Ok(())
407 }
408
409 #[test]
410 fn serde_max_length_slot_name() -> anyhow::Result<()> {
411 let slot_name = StorageSlotName::new(get_max_length_slot_name())?;
412 assert_eq!(slot_name, StorageSlotName::read_from_bytes(&slot_name.to_bytes())?);
413 Ok(())
414 }
415
416 fn get_max_length_slot_name() -> String {
420 const MIDEN_STR: &str = "miden::";
421 let remainder = ['a'; StorageSlotName::MAX_LENGTH - MIDEN_STR.len()];
422 let mut string = MIDEN_STR.to_owned();
423 string.extend(remainder);
424 assert_eq!(string.len(), StorageSlotName::MAX_LENGTH);
425 string
426 }
427}