miden_objects/asset/
token_symbol.rs

1use alloc::string::String;
2
3use super::{AssetError, Felt};
4
5#[derive(Default, Clone, Copy, Debug)]
6pub struct TokenSymbol(Felt);
7
8impl TokenSymbol {
9    pub const MAX_SYMBOL_LENGTH: usize = 6;
10    pub const MAX_ENCODED_VALUE: u64 = 26u64.pow(TokenSymbol::MAX_SYMBOL_LENGTH as u32);
11
12    pub fn new(symbol: &str) -> Result<Self, AssetError> {
13        let felt = encode_symbol_to_felt(symbol)?;
14        Ok(Self(felt))
15    }
16
17    pub fn to_str(&self) -> String {
18        decode_felt_to_symbol(self.0)
19    }
20}
21
22impl From<TokenSymbol> for Felt {
23    fn from(symbol: TokenSymbol) -> Self {
24        symbol.0
25    }
26}
27
28impl TryFrom<&str> for TokenSymbol {
29    type Error = AssetError;
30
31    fn try_from(symbol: &str) -> Result<Self, Self::Error> {
32        TokenSymbol::new(symbol)
33    }
34}
35
36impl TryFrom<Felt> for TokenSymbol {
37    type Error = AssetError;
38
39    fn try_from(felt: Felt) -> Result<Self, Self::Error> {
40        // Check if the felt value is within the valid range
41        if felt.as_int() >= TokenSymbol::MAX_ENCODED_VALUE {
42            return Err(AssetError::TokenSymbolError(format!(
43                "token symbol value {} cannot exceed {}",
44                felt.as_int(),
45                TokenSymbol::MAX_ENCODED_VALUE
46            )));
47        }
48        Ok(TokenSymbol(felt))
49    }
50}
51
52// HELPER FUNCTIONS
53// ================================================================================================
54// Utils to encode and decode the token symbol as a Felt. Token Symbols can consists of up to 6
55// characters , e.g., A = 0, ...
56fn encode_symbol_to_felt(s: &str) -> Result<Felt, AssetError> {
57    if s.is_empty() || s.len() > TokenSymbol::MAX_SYMBOL_LENGTH {
58        return Err(AssetError::TokenSymbolError(format!(
59            "token symbol of length {} is not between 1 and 6 characters long",
60            s.len()
61        )));
62    } else if s.chars().any(|c| !c.is_ascii_uppercase()) {
63        return Err(AssetError::TokenSymbolError(format!(
64            "token symbol {} contains characters that are not uppercase ASCII",
65            s
66        )));
67    }
68
69    let mut encoded_value = 0;
70    for char in s.chars() {
71        let digit = char as u64 - b'A' as u64;
72        assert!(digit < 26);
73        encoded_value = encoded_value * 26 + digit;
74    }
75
76    Ok(Felt::new(encoded_value))
77}
78
79fn decode_felt_to_symbol(encoded_felt: Felt) -> String {
80    let encoded_value = encoded_felt.as_int();
81    assert!(encoded_value < 26u64.pow(TokenSymbol::MAX_SYMBOL_LENGTH as u32));
82
83    let mut decoded_string = String::new();
84    let mut remaining_value = encoded_value;
85
86    for _ in 0..6 {
87        let digit = (remaining_value % 26) as u8;
88        let char = (digit + b'A') as char;
89        decoded_string.insert(0, char);
90        remaining_value /= 26;
91    }
92
93    decoded_string
94}
95
96// TESTS
97// ================================================================================================
98#[test]
99fn test_token_symbol_decoding_encoding() {
100    let symbols = vec!["AAAAAA", "AAAAAB", "AAAAAC", "AAAAAD", "AAAAAE", "AAAAAF", "AAAAAG"];
101    for symbol in symbols {
102        let token_symbol = TokenSymbol::try_from(symbol).unwrap();
103        let decoded_symbol = TokenSymbol::to_str(&token_symbol);
104        assert_eq!(symbol, decoded_symbol);
105    }
106
107    let symbol = "";
108    let felt = encode_symbol_to_felt(symbol);
109    assert!(felt.is_err());
110
111    let symbol = "ABCDEFG";
112    let felt = encode_symbol_to_felt(symbol);
113    assert!(felt.is_err());
114
115    let symbol = "$$$";
116    let felt = encode_symbol_to_felt(symbol);
117    assert!(felt.is_err());
118
119    let symbol = "ABCDEF";
120    let token_symbol = TokenSymbol::try_from(symbol);
121    assert!(token_symbol.is_ok());
122    let token_symbol_felt: Felt = token_symbol.unwrap().into();
123    assert_eq!(token_symbol_felt, encode_symbol_to_felt(symbol).unwrap());
124}