miden_protocol/asset/
token_symbol.rs1use alloc::string::String;
2
3use super::{Felt, TokenSymbolError};
4
5#[derive(Default, Clone, Copy, Debug, PartialEq)]
9pub struct TokenSymbol(Felt);
10
11impl TokenSymbol {
12 pub const MAX_SYMBOL_LENGTH: usize = 12;
14
15 pub const ALPHABET_LENGTH: u64 = 26;
17
18 pub const MAX_ENCODED_VALUE: u64 = 2481152873203736562;
22
23 pub const fn from_static_str(symbol: &'static str) -> Self {
41 match encode_symbol_to_felt(symbol) {
42 Ok(felt) => Self(felt),
43 Err(_) => panic!("invalid token symbol"),
45 }
46 }
47
48 pub fn new(symbol: &str) -> Result<Self, TokenSymbolError> {
55 let felt = encode_symbol_to_felt(symbol)?;
56 Ok(Self(felt))
57 }
58
59 pub fn to_string(&self) -> Result<String, TokenSymbolError> {
67 decode_felt_to_symbol(self.0)
68 }
69}
70
71impl From<TokenSymbol> for Felt {
72 fn from(symbol: TokenSymbol) -> Self {
73 symbol.0
74 }
75}
76
77impl TryFrom<&str> for TokenSymbol {
78 type Error = TokenSymbolError;
79
80 fn try_from(symbol: &str) -> Result<Self, Self::Error> {
81 TokenSymbol::new(symbol)
82 }
83}
84
85impl TryFrom<Felt> for TokenSymbol {
86 type Error = TokenSymbolError;
87
88 fn try_from(felt: Felt) -> Result<Self, Self::Error> {
89 if felt.as_canonical_u64() > Self::MAX_ENCODED_VALUE {
91 return Err(TokenSymbolError::ValueTooLarge(felt.as_canonical_u64()));
92 }
93 Ok(TokenSymbol(felt))
94 }
95}
96
97const fn encode_symbol_to_felt(s: &str) -> Result<Felt, TokenSymbolError> {
118 let bytes = s.as_bytes();
119 let len = bytes.len();
120
121 if len == 0 || len > TokenSymbol::MAX_SYMBOL_LENGTH {
122 return Err(TokenSymbolError::InvalidLength(len));
123 }
124
125 let mut encoded_value: u64 = 0;
126 let mut idx = 0;
127
128 while idx < len {
129 let byte = bytes[idx];
130
131 if !byte.is_ascii_uppercase() {
132 return Err(TokenSymbolError::InvalidCharacter);
133 }
134
135 let digit = (byte - b'A') as u64;
136 encoded_value = encoded_value * TokenSymbol::ALPHABET_LENGTH + digit;
137 idx += 1;
138 }
139
140 encoded_value = encoded_value * TokenSymbol::ALPHABET_LENGTH + len as u64;
142
143 Ok(Felt::new(encoded_value))
144}
145
146fn decode_felt_to_symbol(encoded_felt: Felt) -> Result<String, TokenSymbolError> {
168 let encoded_value = encoded_felt.as_canonical_u64();
169
170 if encoded_value > TokenSymbol::MAX_ENCODED_VALUE {
172 return Err(TokenSymbolError::ValueTooLarge(encoded_value));
173 }
174
175 let mut decoded_string = String::new();
176 let mut remaining_value = encoded_value;
177
178 let token_len = (remaining_value % TokenSymbol::ALPHABET_LENGTH) as usize;
180 if token_len == 0 || token_len > TokenSymbol::MAX_SYMBOL_LENGTH {
181 return Err(TokenSymbolError::InvalidLength(token_len));
182 }
183 remaining_value /= TokenSymbol::ALPHABET_LENGTH;
184
185 for _ in 0..token_len {
186 let digit = (remaining_value % TokenSymbol::ALPHABET_LENGTH) as u8;
187 let char = (digit + b'A') as char;
188 decoded_string.insert(0, char);
189 remaining_value /= TokenSymbol::ALPHABET_LENGTH;
190 }
191
192 if remaining_value != 0 {
195 return Err(TokenSymbolError::DataNotFullyDecoded);
196 }
197
198 Ok(decoded_string)
199}
200
201#[cfg(test)]
205mod test {
206 use assert_matches::assert_matches;
207
208 use super::{
209 Felt,
210 TokenSymbol,
211 TokenSymbolError,
212 decode_felt_to_symbol,
213 encode_symbol_to_felt,
214 };
215
216 #[test]
217 fn test_token_symbol_decoding_encoding() {
218 let symbols = vec![
219 "AAAAAA",
220 "AAAAB",
221 "AAAC",
222 "ABC",
223 "BC",
224 "A",
225 "B",
226 "ZZZZZZ",
227 "ABCDEFGH",
228 "MIDENCRYPTO",
229 "ZZZZZZZZZZZZ",
230 ];
231 for symbol in symbols {
232 let token_symbol = TokenSymbol::try_from(symbol).unwrap();
233 let decoded_symbol = TokenSymbol::to_string(&token_symbol).unwrap();
234 assert_eq!(symbol, decoded_symbol);
235 }
236
237 let symbol = "";
238 let felt = encode_symbol_to_felt(symbol);
239 assert_matches!(felt.unwrap_err(), TokenSymbolError::InvalidLength(0));
240
241 let symbol = "ABCDEFGHIJKLM";
242 let felt = encode_symbol_to_felt(symbol);
243 assert_matches!(felt.unwrap_err(), TokenSymbolError::InvalidLength(13));
244
245 let symbol = "$$$";
246 let felt = encode_symbol_to_felt(symbol);
247 assert_matches!(felt.unwrap_err(), TokenSymbolError::InvalidCharacter);
248
249 let symbol = "ABCDEFGHIJKL";
250 let token_symbol = TokenSymbol::try_from(symbol);
251 assert!(token_symbol.is_ok());
252 let token_symbol_felt: Felt = token_symbol.unwrap().into();
253 assert_eq!(token_symbol_felt, encode_symbol_to_felt(symbol).unwrap());
254 }
255
256 #[test]
260 fn test_invalid_token_len() {
261 let encoded_symbol = TokenSymbol::try_from("ABCDEF").unwrap();
263
264 let invalid_encoded_symbol_u64 = Felt::from(encoded_symbol).as_canonical_u64() - 3;
266
267 let err = decode_felt_to_symbol(Felt::new(invalid_encoded_symbol_u64)).unwrap_err();
270 assert_matches!(err, TokenSymbolError::DataNotFullyDecoded);
271 }
272
273 #[test]
276 fn test_token_symbol_max_value() {
277 let token_symbol = TokenSymbol::try_from("ZZZZZZZZZZZZ").unwrap();
278 assert_eq!(Felt::from(token_symbol).as_canonical_u64(), TokenSymbol::MAX_ENCODED_VALUE);
279 }
280
281 const _TOKEN0: TokenSymbol = TokenSymbol::from_static_str("A");
285 const _TOKEN1: TokenSymbol = TokenSymbol::from_static_str("ETH");
286 const _TOKEN2: TokenSymbol = TokenSymbol::from_static_str("MIDEN");
287 const _TOKEN3: TokenSymbol = TokenSymbol::from_static_str("ZZZZZZ");
288 const _TOKEN4: TokenSymbol = TokenSymbol::from_static_str("ABCDEFGH");
289 const _TOKEN5: TokenSymbol = TokenSymbol::from_static_str("ZZZZZZZZZZZZ");
290
291 #[test]
292 fn test_from_static_str_matches_new() {
293 let symbols = ["A", "BC", "ETH", "MIDEN", "ZZZZZZ", "ABCDEFGH", "ZZZZZZZZZZZZ"];
295 for symbol in symbols {
296 let from_new = TokenSymbol::new(symbol).unwrap();
297 let from_static = TokenSymbol::from_static_str(symbol);
298 assert_eq!(
299 Felt::from(from_new),
300 Felt::from(from_static),
301 "Mismatch for symbol: {}",
302 symbol
303 );
304 }
305 }
306
307 #[test]
308 #[should_panic(expected = "invalid token symbol")]
309 fn token_symbol_panics_on_empty_string() {
310 TokenSymbol::from_static_str("");
311 }
312
313 #[test]
314 #[should_panic(expected = "invalid token symbol")]
315 fn token_symbol_panics_on_too_long_string() {
316 TokenSymbol::from_static_str("ABCDEFGHIJKLM");
317 }
318
319 #[test]
320 #[should_panic(expected = "invalid token symbol")]
321 fn token_symbol_panics_on_lowercase() {
322 TokenSymbol::from_static_str("eth");
323 }
324
325 #[test]
326 #[should_panic(expected = "invalid token symbol")]
327 fn token_symbol_panics_on_invalid_character() {
328 TokenSymbol::from_static_str("ET$");
329 }
330
331 #[test]
332 #[should_panic(expected = "invalid token symbol")]
333 fn token_symbol_panics_on_number() {
334 TokenSymbol::from_static_str("ETH1");
335 }
336}