miden_protocol/asset/
token_symbol.rs1use alloc::fmt;
2
3use super::{Felt, TokenSymbolError};
4use crate::utils::ShortCapitalString;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct TokenSymbol(ShortCapitalString);
14
15impl TokenSymbol {
16 pub const ALPHABET: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
18
19 pub const MAX_SYMBOL_LENGTH: usize = 12;
21
22 pub const MIN_ENCODED_VALUE: u64 = 1;
26
27 pub const MAX_ENCODED_VALUE: u64 = 2481152873203736562;
31
32 pub fn new_unchecked(symbol: &str) -> Self {
40 Self::new(symbol).expect("invalid token symbol")
41 }
42
43 pub fn new(symbol: &str) -> Result<Self, TokenSymbolError> {
50 ShortCapitalString::from_ascii_uppercase(symbol).map(Self).map_err(Into::into)
51 }
52
53 pub fn as_element(&self) -> Felt {
66 self.0.as_element(Self::ALPHABET).expect("TokenSymbol alphabet is always valid")
67 }
68}
69
70impl fmt::Display for TokenSymbol {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 self.0.fmt(f)
73 }
74}
75
76impl From<TokenSymbol> for Felt {
77 fn from(symbol: TokenSymbol) -> Self {
78 symbol.as_element()
79 }
80}
81
82impl From<&TokenSymbol> for Felt {
83 fn from(symbol: &TokenSymbol) -> Self {
84 symbol.as_element()
85 }
86}
87
88impl TryFrom<&str> for TokenSymbol {
89 type Error = TokenSymbolError;
90
91 fn try_from(symbol: &str) -> Result<Self, Self::Error> {
92 TokenSymbol::new(symbol)
93 }
94}
95
96impl TryFrom<Felt> for TokenSymbol {
112 type Error = TokenSymbolError;
113
114 fn try_from(felt: Felt) -> Result<Self, Self::Error> {
115 ShortCapitalString::try_from_encoded_felt(
116 felt,
117 Self::ALPHABET,
118 Self::MIN_ENCODED_VALUE,
119 Self::MAX_ENCODED_VALUE,
120 )
121 .map(Self)
122 .map_err(Into::into)
123 }
124}
125
126#[cfg(test)]
130mod test {
131 use alloc::string::ToString;
132
133 use assert_matches::assert_matches;
134
135 use super::{Felt, TokenSymbol, TokenSymbolError};
136
137 #[test]
138 fn test_token_symbol_decoding_encoding() {
139 let symbols = vec![
140 "AAAAAA",
141 "AAAAB",
142 "AAAC",
143 "ABC",
144 "BC",
145 "A",
146 "B",
147 "ZZZZZZ",
148 "ABCDEFGH",
149 "MIDENCRYPTO",
150 "ZZZZZZZZZZZZ",
151 ];
152 for symbol in symbols {
153 let token_symbol = TokenSymbol::try_from(symbol).unwrap();
154 let decoded_symbol = token_symbol.to_string();
155 assert_eq!(symbol, decoded_symbol);
156 }
157
158 let err = TokenSymbol::new("").unwrap_err();
159 assert_matches!(err, TokenSymbolError::InvalidLength(0));
160
161 let err = TokenSymbol::new("ABCDEFGHIJKLM").unwrap_err();
162 assert_matches!(err, TokenSymbolError::InvalidLength(13));
163
164 let err = TokenSymbol::new("$$$").unwrap_err();
165 assert_matches!(err, TokenSymbolError::InvalidCharacter);
166
167 let symbol = "ABCDEFGHIJKL";
168 let token_symbol = TokenSymbol::new(symbol).unwrap();
169 let token_symbol_felt: Felt = token_symbol.into();
170 assert_eq!(token_symbol_felt, TokenSymbol::new(symbol).unwrap().as_element());
171 }
172
173 #[test]
176 fn test_invalid_token_len() {
177 let encoded_symbol = TokenSymbol::try_from("ABCDEF").unwrap();
179
180 let invalid_encoded_symbol_u64 = Felt::from(encoded_symbol).as_canonical_u64() - 3;
182
183 let err =
185 TokenSymbol::try_from(Felt::new_unchecked(invalid_encoded_symbol_u64)).unwrap_err();
186 assert_matches!(err, TokenSymbolError::DataNotFullyDecoded);
187 }
188
189 #[test]
192 fn test_token_symbol_max_value() {
193 let token_symbol = TokenSymbol::try_from("ZZZZZZZZZZZZ").unwrap();
194 assert_eq!(Felt::from(token_symbol).as_canonical_u64(), TokenSymbol::MAX_ENCODED_VALUE);
195 }
196
197 #[test]
200 fn test_token_symbol_min_value() {
201 let token_symbol = TokenSymbol::try_from("A").unwrap();
202 assert_eq!(Felt::from(token_symbol).as_canonical_u64(), TokenSymbol::MIN_ENCODED_VALUE);
203 }
204
205 #[test]
207 fn test_token_symbol_underflow() {
208 let err = TokenSymbol::try_from(Felt::ZERO).unwrap_err();
209 assert_matches!(err, TokenSymbolError::ValueTooSmall(0));
210 }
211
212 #[test]
216 fn test_new_unchecked_matches_new() {
217 let symbols = ["A", "BC", "ETH", "MIDEN", "ZZZZZZ", "ABCDEFGH", "ZZZZZZZZZZZZ"];
219 for symbol in symbols {
220 let from_new = TokenSymbol::new(symbol).unwrap();
221 let from_static = TokenSymbol::new_unchecked(symbol);
222 assert_eq!(from_new, from_static, "Mismatch for symbol: {}", symbol);
223 }
224 }
225
226 #[test]
227 #[should_panic(expected = "invalid token symbol")]
228 fn token_symbol_panics_on_empty_string() {
229 TokenSymbol::new_unchecked("");
230 }
231
232 #[test]
233 #[should_panic(expected = "invalid token symbol")]
234 fn token_symbol_panics_on_too_long_string() {
235 TokenSymbol::new_unchecked("ABCDEFGHIJKLM");
236 }
237
238 #[test]
239 #[should_panic(expected = "invalid token symbol")]
240 fn token_symbol_panics_on_lowercase() {
241 TokenSymbol::new_unchecked("eth");
242 }
243
244 #[test]
245 #[should_panic(expected = "invalid token symbol")]
246 fn token_symbol_panics_on_invalid_character() {
247 TokenSymbol::new_unchecked("ET$");
248 }
249
250 #[test]
251 #[should_panic(expected = "invalid token symbol")]
252 fn token_symbol_panics_on_number() {
253 TokenSymbol::new_unchecked("ETH1");
254 }
255}