miden_objects/account/component/template/storage/
placeholder.rs

1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::string::{String, ToString};
4use core::error::Error;
5use core::fmt::{self, Display};
6
7use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
8use miden_core::{Felt, Word};
9use miden_crypto::dsa::{ecdsa_k256_keccak, rpo_falcon512};
10use miden_processor::DeserializationError;
11use thiserror::Error;
12
13use crate::asset::TokenSymbol;
14use crate::utils::sync::LazyLock;
15
16/// A global registry for template converters.
17///
18/// It is used during component instantiation to dynamically convert template placeholders into
19/// their respective storage values.
20pub static TEMPLATE_REGISTRY: LazyLock<TemplateRegistry> = LazyLock::new(|| {
21    let mut registry = TemplateRegistry::new();
22    registry.register_felt_type::<u8>();
23    registry.register_felt_type::<u16>();
24    registry.register_felt_type::<u32>();
25    registry.register_felt_type::<Felt>();
26    registry.register_felt_type::<TokenSymbol>();
27    registry.register_word_type::<Word>();
28    registry.register_word_type::<rpo_falcon512::PublicKey>();
29    registry.register_word_type::<ecdsa_k256_keccak::PublicKey>();
30    registry
31});
32
33// STORAGE VALUE NAME
34// ================================================================================================
35
36/// A simple wrapper type around a string key that identifies values.
37///
38/// A storage value name is a string that identifies dynamic values within a component's metadata
39/// storage entries.
40///
41/// These names can be chained together, in a way that allows composing unique keys for inner
42/// templated elements.
43///
44/// At component instantiation, a map of names to values must be provided to dynamically
45/// replace these placeholders with the instance's actual values.
46#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
47#[cfg_attr(feature = "std", derive(::serde::Deserialize, ::serde::Serialize))]
48#[cfg_attr(feature = "std", serde(transparent))]
49pub struct StorageValueName {
50    fully_qualified_name: String,
51}
52
53impl StorageValueName {
54    /// Creates a new [`StorageValueName`] from the provided string.
55    ///
56    /// A [`StorageValueName`] serves as an identifier for storage values that are determined at
57    /// instantiation time of an [AccountComponentTemplate](super::super::AccountComponentTemplate).
58    ///
59    /// The key can consist of one or more segments separated by dots (`.`).
60    /// Each segment must be non-empty and may contain only alphanumeric characters, underscores
61    /// (`_`), or hyphens (`-`).
62    ///
63    /// # Errors
64    ///
65    /// This method returns an error if:
66    /// - Any segment (or the whole key) is empty.
67    /// - Any segment contains invalid characters.
68    pub fn new(base: impl Into<String>) -> Result<Self, StorageValueNameError> {
69        let base: String = base.into();
70        for segment in base.split('.') {
71            Self::validate_segment(segment)?;
72        }
73        Ok(Self { fully_qualified_name: base })
74    }
75
76    /// Creates an empty [`StorageValueName`].
77    pub(crate) fn empty() -> Self {
78        StorageValueName { fully_qualified_name: String::default() }
79    }
80
81    /// Adds a suffix to the storage value name, separated by a period.
82    #[must_use]
83    pub fn with_suffix(self, suffix: &StorageValueName) -> StorageValueName {
84        let mut key = self;
85        if !suffix.as_str().is_empty() {
86            if !key.as_str().is_empty() {
87                key.fully_qualified_name.push('.');
88            }
89            key.fully_qualified_name.push_str(suffix.as_str());
90        }
91
92        key
93    }
94
95    /// Returns the fully qualified name as a string slice.
96    pub fn as_str(&self) -> &str {
97        &self.fully_qualified_name
98    }
99
100    fn validate_segment(segment: &str) -> Result<(), StorageValueNameError> {
101        if segment.is_empty() {
102            return Err(StorageValueNameError::EmptySegment);
103        }
104        if let Some(offending_char) =
105            segment.chars().find(|&c| !(c.is_ascii_alphanumeric() || c == '_' || c == '-'))
106        {
107            return Err(StorageValueNameError::InvalidCharacter {
108                part: segment.to_string(),
109                character: offending_char,
110            });
111        }
112
113        Ok(())
114    }
115}
116
117impl Display for StorageValueName {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        f.write_str(self.as_str())
120    }
121}
122
123impl Serializable for StorageValueName {
124    fn write_into<W: ByteWriter>(&self, target: &mut W) {
125        target.write(&self.fully_qualified_name);
126    }
127}
128
129impl Deserializable for StorageValueName {
130    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
131        let key: String = source.read()?;
132        Ok(StorageValueName { fully_qualified_name: key })
133    }
134}
135
136#[derive(Debug, Error)]
137pub enum StorageValueNameError {
138    #[error("key segment is empty")]
139    EmptySegment,
140    #[error("key segment '{part}' contains invalid character '{character}'")]
141    InvalidCharacter { part: String, character: char },
142}
143
144// TEMPLATE TYPE ERROR
145// ================================================================================================
146
147/// Errors that can occur when parsing or converting template types.
148///
149/// This enum covers various failure cases including parsing errors, conversion errors,
150/// unsupported conversions, and cases where a required type is not found in the registry.
151#[derive(Debug, Error)]
152pub enum TemplateTypeError {
153    #[error("conversion error: {0}")]
154    ConversionError(String),
155    #[error("felt type ` {0}` not found in the type registry")]
156    FeltTypeNotFound(TemplateType),
157    #[error("invalid type name `{0}`: {1}")]
158    InvalidTypeName(String, String),
159    #[error("failed to parse input `{input}` as `{template_type}`")]
160    ParseError {
161        input: String,
162        template_type: TemplateType,
163        source: Box<dyn Error + Send + Sync + 'static>,
164    },
165    #[error("word type ` {0}` not found in the type registry")]
166    WordTypeNotFound(TemplateType),
167}
168
169impl TemplateTypeError {
170    /// Creates a [`TemplateTypeError::ParseError`].
171    pub fn parse(
172        input: impl Into<String>,
173        template_type: TemplateType,
174        source: impl Error + Send + Sync + 'static,
175    ) -> Self {
176        TemplateTypeError::ParseError {
177            input: input.into(),
178            template_type,
179            source: Box::new(source),
180        }
181    }
182}
183
184// TEMPLATE TYPE
185// ================================================================================================
186
187/// A newtype wrapper around a `String`, representing a template's type identifier.
188#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
189#[cfg_attr(feature = "std", derive(::serde::Deserialize, ::serde::Serialize))]
190#[cfg_attr(feature = "std", serde(transparent))]
191pub struct TemplateType(String);
192
193impl TemplateType {
194    /// Creates a new [`TemplateType`] from a `String`.
195    ///
196    /// The name must follow a Rust-style namespace format, consisting of one or more segments
197    /// (non-empty, and alphanumerical) separated by double-colon (`::`) delimiters.
198    ///
199    /// # Errors
200    ///
201    /// - If the identifier is empty.
202    /// - If it is composed of one or more segments separated by `::`.
203    /// - If any segment is empty or contains something other than alphanumerical
204    ///   characters/underscores.
205    pub fn new(s: impl Into<String>) -> Result<Self, TemplateTypeError> {
206        let s = s.into();
207        if s.is_empty() {
208            return Err(TemplateTypeError::InvalidTypeName(
209                s.clone(),
210                "template type identifier is empty".to_string(),
211            ));
212        }
213        for segment in s.split("::") {
214            if segment.is_empty() {
215                return Err(TemplateTypeError::InvalidTypeName(
216                    s.clone(),
217                    "empty segment in template type identifier".to_string(),
218                ));
219            }
220            if !segment.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
221                return Err(TemplateTypeError::InvalidTypeName(
222                    s.clone(),
223                    format!("segment '{segment}' contains invalid characters"),
224                ));
225            }
226        }
227        Ok(Self(s))
228    }
229
230    /// Returns the [`TemplateType`] for the native [`Felt`] type.
231    pub fn native_felt() -> TemplateType {
232        TemplateType::new("felt").expect("type is well formed")
233    }
234
235    /// Returns the [`TemplateType`] for the native [`Word`] type.
236    pub fn native_word() -> TemplateType {
237        TemplateType::new("word").expect("type is well formed")
238    }
239
240    /// Returns the [`TemplateType`] for storage map placeholders.
241    pub fn storage_map() -> TemplateType {
242        TemplateType::new("map").expect("type is well formed")
243    }
244
245    /// Returns a reference to the inner string.
246    pub fn as_str(&self) -> &str {
247        &self.0
248    }
249}
250
251impl Display for TemplateType {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        f.write_str(self.as_str())
254    }
255}
256
257impl Serializable for TemplateType {
258    fn write_into<W: ByteWriter>(&self, target: &mut W) {
259        target.write(self.0.clone())
260    }
261}
262
263impl Deserializable for TemplateType {
264    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
265        let id: String = source.read()?;
266
267        TemplateType::new(id).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
268    }
269}
270
271// TEMPLATE REQUIREMENT
272// ================================================================================================
273
274/// Describes the expected type and additional metadata for a templated storage entry.
275///
276/// A `PlaceholderTypeRequirement` specifies the expected type identifier for a storage entry as
277/// well as an optional description. This information is used to validate and provide context for
278/// dynamic storage values.
279#[derive(Debug)]
280pub struct PlaceholderTypeRequirement {
281    /// The expected type identifier.
282    pub r#type: TemplateType,
283    /// An optional description providing additional context.
284    pub description: Option<String>,
285}
286
287// TEMPLATE TRAITS
288// ================================================================================================
289
290/// Trait for converting a string into a single `Felt`.
291pub trait TemplateFelt {
292    /// Returns the type identifier.
293    fn type_name() -> TemplateType;
294    /// Parses the input string into a `Felt`.
295    fn parse_felt(input: &str) -> Result<Felt, TemplateTypeError>;
296}
297
298/// Trait for converting a string into a single `Word`.
299pub trait TemplateWord {
300    /// Returns the type identifier.
301    fn type_name() -> TemplateType;
302    /// Parses the input string into a `Word`.
303    fn parse_word(input: &str) -> Result<Word, TemplateTypeError>;
304}
305
306// FELT IMPLS FOR NATIVE TYPES
307// ================================================================================================
308
309impl TemplateFelt for u8 {
310    fn type_name() -> TemplateType {
311        TemplateType::new("u8").expect("type is well formed")
312    }
313
314    fn parse_felt(input: &str) -> Result<Felt, TemplateTypeError> {
315        let native: u8 = input
316            .parse()
317            .map_err(|err| TemplateTypeError::parse(input.to_string(), Self::type_name(), err))?;
318        Ok(Felt::from(native))
319    }
320}
321
322impl TemplateFelt for u16 {
323    fn type_name() -> TemplateType {
324        TemplateType::new("u16").expect("type is well formed")
325    }
326
327    fn parse_felt(input: &str) -> Result<Felt, TemplateTypeError> {
328        let native: u16 = input
329            .parse()
330            .map_err(|err| TemplateTypeError::parse(input.to_string(), Self::type_name(), err))?;
331        Ok(Felt::from(native))
332    }
333}
334
335impl TemplateFelt for u32 {
336    fn type_name() -> TemplateType {
337        TemplateType::new("u32").expect("type is well formed")
338    }
339
340    fn parse_felt(input: &str) -> Result<Felt, TemplateTypeError> {
341        let native: u32 = input
342            .parse()
343            .map_err(|err| TemplateTypeError::parse(input.to_string(), Self::type_name(), err))?;
344        Ok(Felt::from(native))
345    }
346}
347
348impl TemplateFelt for Felt {
349    fn type_name() -> TemplateType {
350        TemplateType::new("felt").expect("type is well formed")
351    }
352
353    fn parse_felt(input: &str) -> Result<Felt, TemplateTypeError> {
354        let n = if let Some(hex) = input.strip_prefix("0x").or_else(|| input.strip_prefix("0X")) {
355            u64::from_str_radix(hex, 16)
356        } else {
357            input.parse::<u64>()
358        }
359        .map_err(|err| TemplateTypeError::parse(input.to_string(), Self::type_name(), err))?;
360        Felt::try_from(n).map_err(|_| TemplateTypeError::ConversionError(input.to_string()))
361    }
362}
363
364impl TemplateFelt for TokenSymbol {
365    fn type_name() -> TemplateType {
366        TemplateType::new("token_symbol").expect("type is well formed")
367    }
368    fn parse_felt(input: &str) -> Result<Felt, TemplateTypeError> {
369        let token = TokenSymbol::new(input)
370            .map_err(|err| TemplateTypeError::parse(input.to_string(), Self::type_name(), err))?;
371        Ok(Felt::from(token))
372    }
373}
374
375// WORD IMPLS FOR NATIVE TYPES
376// ================================================================================================
377
378#[derive(Debug, Error)]
379#[error("error parsing word: {0}")]
380struct WordParseError(String);
381
382/// Pads a hex string to 64 characters (excluding the 0x prefix).
383///
384/// If the input starts with "0x" and has fewer than 64 hex characters after the prefix,
385/// it will be left-padded with zeros. Otherwise, returns the input unchanged.
386fn pad_hex_string(input: &str) -> String {
387    if input.starts_with("0x") && input.len() < 66 {
388        // 66 = "0x" + 64 hex chars
389        let hex_part = &input[2..];
390        let padding = "0".repeat(64 - hex_part.len());
391        format!("0x{}{}", padding, hex_part)
392    } else {
393        input.to_string()
394    }
395}
396
397impl TemplateWord for Word {
398    fn type_name() -> TemplateType {
399        TemplateType::native_word()
400    }
401    fn parse_word(input: &str) -> Result<Word, TemplateTypeError> {
402        let padded_input = pad_hex_string(input);
403
404        Word::try_from(padded_input.as_str()).map_err(|err| {
405            TemplateTypeError::parse(
406                input.to_string(), // Use original input in error
407                Self::type_name(),
408                WordParseError(err.to_string()),
409            )
410        })
411    }
412}
413
414impl TemplateWord for rpo_falcon512::PublicKey {
415    fn type_name() -> TemplateType {
416        TemplateType::new("auth::rpo_falcon512::pub_key").expect("type is well formed")
417    }
418    fn parse_word(input: &str) -> Result<Word, TemplateTypeError> {
419        let padded_input = pad_hex_string(input);
420
421        Word::try_from(padded_input.as_str()).map_err(|err| {
422            TemplateTypeError::parse(
423                input.to_string(), // Use original input in error
424                Self::type_name(),
425                WordParseError(err.to_string()),
426            )
427        })
428    }
429}
430
431impl TemplateWord for ecdsa_k256_keccak::PublicKey {
432    fn type_name() -> TemplateType {
433        TemplateType::new("auth::ecdsa_k256_keccak::pub_key").expect("type is well formed")
434    }
435    fn parse_word(input: &str) -> Result<Word, TemplateTypeError> {
436        let padded_input = pad_hex_string(input);
437
438        Word::try_from(padded_input.as_str()).map_err(|err| {
439            TemplateTypeError::parse(
440                input.to_string(),
441                Self::type_name(),
442                WordParseError(err.to_string()),
443            )
444        })
445    }
446}
447
448// TYPE ALIASES FOR CONVERTER CLOSURES
449// ================================================================================================
450
451/// Type alias for a function that converts a string into a [`Felt`] value.
452type TemplateFeltConverter = fn(&str) -> Result<Felt, TemplateTypeError>;
453
454/// Type alias for a function that converts a string into a [`Word`].
455type TemplateWordConverter = fn(&str) -> Result<Word, TemplateTypeError>;
456
457// TODO: Implement converting to list of words for multi-slot values
458
459// TEMPLATE REGISTRY
460// ================================================================================================
461
462/// Registry for template converters.
463///
464/// This registry maintains mappings from type identifiers (as strings) to conversion functions for
465/// [`Felt`], [`Word`], and [`Vec<Word>`] types. It is used to dynamically parse template inputs
466/// into their corresponding storage representations.
467#[derive(Clone, Debug, Default)]
468pub struct TemplateRegistry {
469    felt: BTreeMap<TemplateType, TemplateFeltConverter>,
470    word: BTreeMap<TemplateType, TemplateWordConverter>,
471}
472
473impl TemplateRegistry {
474    /// Creates a new, empty `TemplateRegistry`.
475    ///
476    /// The registry is initially empty and conversion functions can be registered using the
477    /// `register_*_type` methods.
478    pub fn new() -> Self {
479        Self { ..Default::default() }
480    }
481
482    /// Registers a `TemplateFelt` converter, to interpret a string as a [`Felt``].
483    pub fn register_felt_type<T: TemplateFelt + 'static>(&mut self) {
484        let key = T::type_name();
485        self.felt.insert(key, T::parse_felt);
486    }
487
488    /// Registers a `TemplateWord` converter, to interpret a string as a [`Word`].
489    pub fn register_word_type<T: TemplateWord + 'static>(&mut self) {
490        let key = T::type_name();
491        self.word.insert(key, T::parse_word);
492    }
493
494    /// Attempts to parse a string into a `Felt` using the registered converter for the given type
495    /// name.
496    ///
497    /// # Arguments
498    ///
499    /// - type_name: A string that acts as the type identifier.
500    /// - value: The string representation of the value to be parsed.
501    ///
502    /// # Errors
503    ///
504    /// - If the type is not registered or if the conversion fails.
505    pub fn try_parse_felt(
506        &self,
507        type_name: &TemplateType,
508        value: &str,
509    ) -> Result<Felt, TemplateTypeError> {
510        let converter = self
511            .felt
512            .get(type_name)
513            .ok_or(TemplateTypeError::FeltTypeNotFound(type_name.clone()))?;
514        converter(value)
515    }
516
517    /// Attempts to parse a string into a `Word` using the registered converter for the given type
518    /// name.
519    ///
520    /// # Arguments
521    ///
522    /// - type_name: A string that acts as the type identifier.
523    /// - value: The string representation of the value to be parsed.
524    ///
525    /// # Errors
526    ///
527    /// - If the type is not registered or if the conversion fails.
528    pub fn try_parse_word(
529        &self,
530        type_name: &TemplateType,
531        value: &str,
532    ) -> Result<Word, TemplateTypeError> {
533        let converter = self
534            .word
535            .get(type_name)
536            .ok_or(TemplateTypeError::WordTypeNotFound(type_name.clone()))?;
537        converter(value)
538    }
539
540    /// Returns `true` if a `TemplateFelt` is registered for the given type.
541    pub fn contains_felt_type(&self, type_name: &TemplateType) -> bool {
542        self.felt.contains_key(type_name)
543    }
544
545    /// Returns `true` if a `TemplateWord` is registered for the given type.
546    pub fn contains_word_type(&self, type_name: &TemplateType) -> bool {
547        self.word.contains_key(type_name)
548    }
549}