1use alloc::string::*;
2
3use crate::crypto::compute_hash_on_elements;
4
5use sha3::{Digest, Keccak256};
6use starknet_crypto::pedersen_hash;
7use starknet_types_core::felt::Felt;
8use starknet_types_core::felt::NonZeroFelt;
9
10const DEFAULT_ENTRY_POINT_NAME: &str = "__default__";
11const DEFAULT_L1_ENTRY_POINT_NAME: &str = "__l1_default__";
12
13const ADDR_BOUND: NonZeroFelt = NonZeroFelt::from_raw([
15 576459263475590224,
16 18446744073709255680,
17 160989183,
18 18446743986131443745,
19]);
20
21const CONTRACT_ADDRESS_PREFIX: Felt = Felt::from_raw([
23 533439743893157637,
24 8635008616843941496,
25 17289941567720117366,
26 3829237882463328880,
27]);
28
29#[derive(Debug, Clone)]
31pub enum UdcUniqueness {
32 NotUnique,
35 Unique(UdcUniqueSettings),
39}
40
41#[derive(Debug, Clone)]
43pub struct UdcUniqueSettings {
44 pub deployer_address: Felt,
46 pub udc_contract_address: Felt,
48}
49
50mod errors {
51 use core::fmt::{Display, Formatter, Result};
52
53 #[derive(Debug)]
55 pub struct NonAsciiNameError;
56
57 #[derive(Debug)]
59 pub enum CairoShortStringToFeltError {
60 NonAsciiCharacter,
62 StringTooLong,
64 }
65
66 #[derive(Debug)]
68 pub enum ParseCairoShortStringError {
69 ValueOutOfRange,
71 UnexpectedNullTerminator,
73 }
74
75 #[cfg(feature = "std")]
76 impl std::error::Error for NonAsciiNameError {}
77
78 impl Display for NonAsciiNameError {
79 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
80 write!(f, "the provided name contains non-ASCII characters")
81 }
82 }
83
84 #[cfg(feature = "std")]
85 impl std::error::Error for CairoShortStringToFeltError {}
86
87 impl Display for CairoShortStringToFeltError {
88 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
89 match self {
90 Self::NonAsciiCharacter => {
91 write!(f, "Cairo string can only contain ASCII characters")
92 }
93 Self::StringTooLong => {
94 write!(f, "short string exceeds maximum length of 31 characters")
95 }
96 }
97 }
98 }
99
100 #[cfg(feature = "std")]
101 impl std::error::Error for ParseCairoShortStringError {}
102
103 impl Display for ParseCairoShortStringError {
104 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
105 match self {
106 Self::ValueOutOfRange => write!(f, "field element value out of range"),
107 Self::UnexpectedNullTerminator => write!(f, "unexpected null terminator"),
108 }
109 }
110 }
111}
112pub use errors::{CairoShortStringToFeltError, NonAsciiNameError, ParseCairoShortStringError};
113
114pub fn starknet_keccak(data: &[u8]) -> Felt {
117 let mut hasher = Keccak256::new();
118 hasher.update(data);
119 let mut hash = hasher.finalize();
120
121 hash[0] &= 0b00000011;
123
124 Felt::from_bytes_be(unsafe { &*(hash[..].as_ptr() as *const [u8; 32]) })
126}
127
128pub fn get_selector_from_name(func_name: &str) -> Result<Felt, NonAsciiNameError> {
134 if func_name == DEFAULT_ENTRY_POINT_NAME || func_name == DEFAULT_L1_ENTRY_POINT_NAME {
135 Ok(Felt::ZERO)
136 } else {
137 let name_bytes = func_name.as_bytes();
138 if name_bytes.is_ascii() {
139 Ok(starknet_keccak(name_bytes))
140 } else {
141 Err(NonAsciiNameError)
142 }
143 }
144}
145
146pub fn get_storage_var_address(var_name: &str, args: &[Felt]) -> Result<Felt, NonAsciiNameError> {
152 let var_name_bytes = var_name.as_bytes();
153 if var_name_bytes.is_ascii() {
154 let mut res = starknet_keccak(var_name_bytes);
155 for arg in args {
156 res = pedersen_hash(&res, arg);
157 }
158 Ok(normalize_address(res))
159 } else {
160 Err(NonAsciiNameError)
161 }
162}
163
164pub fn cairo_short_string_to_felt(str: &str) -> Result<Felt, CairoShortStringToFeltError> {
166 if !str.is_ascii() {
167 return Err(CairoShortStringToFeltError::NonAsciiCharacter);
168 }
169 if str.len() > 31 {
170 return Err(CairoShortStringToFeltError::StringTooLong);
171 }
172
173 let ascii_bytes = str.as_bytes();
174
175 let mut buffer = [0u8; 32];
176 buffer[(32 - ascii_bytes.len())..].copy_from_slice(ascii_bytes);
177
178 Ok(Felt::from_bytes_be(&buffer))
180}
181
182pub fn parse_cairo_short_string(felt: &Felt) -> Result<String, ParseCairoShortStringError> {
184 if felt == &Felt::ZERO {
185 return Ok(String::new());
186 }
187
188 let be_bytes = felt.to_bytes_be();
189 if be_bytes[0] > 0 {
190 return Err(ParseCairoShortStringError::ValueOutOfRange);
191 }
192
193 let mut buffer = String::with_capacity(31);
194 for byte in be_bytes {
195 if byte == 0u8 {
196 if !buffer.is_empty() {
197 return Err(ParseCairoShortStringError::UnexpectedNullTerminator);
198 }
199 } else {
200 buffer.push(byte as char)
201 }
202 }
203 Ok(buffer)
204}
205
206pub fn get_contract_address(
215 salt: Felt,
216 class_hash: Felt,
217 constructor_calldata: &[Felt],
218 deployer_address: Felt,
219) -> Felt {
220 normalize_address(compute_hash_on_elements(&[
221 CONTRACT_ADDRESS_PREFIX,
222 deployer_address,
223 salt,
224 class_hash,
225 compute_hash_on_elements(constructor_calldata),
226 ]))
227}
228
229pub fn get_udc_deployed_address(
231 salt: Felt,
232 class_hash: Felt,
233 uniqueness: &UdcUniqueness,
234 constructor_calldata: &[Felt],
235) -> Felt {
236 match uniqueness {
237 UdcUniqueness::NotUnique => {
238 get_contract_address(salt, class_hash, constructor_calldata, Felt::ZERO)
239 }
240 UdcUniqueness::Unique(settings) => {
241 let unique_salt = pedersen_hash(&settings.deployer_address, &salt);
242 get_contract_address(
243 unique_salt,
244 class_hash,
245 constructor_calldata,
246 settings.udc_contract_address,
247 )
248 }
249 }
250}
251
252pub fn normalize_address(address: Felt) -> Felt {
254 address.mod_floor(&ADDR_BOUND)
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
263 fn test_starknet_keccak() {
264 let data = b"execute";
266 let expected_hash =
267 Felt::from_hex("0240060cdb34fcc260f41eac7474ee1d7c80b7e3607daff9ac67c7ea2ebb1c44")
268 .unwrap();
269
270 let hash = starknet_keccak(data);
271
272 assert_eq!(hash, expected_hash);
273 }
274
275 #[test]
276 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
277 fn test_get_selector_from_name() {
278 let func_name = "execute";
280 let expected_selector =
281 Felt::from_hex("0240060cdb34fcc260f41eac7474ee1d7c80b7e3607daff9ac67c7ea2ebb1c44")
282 .unwrap();
283
284 let selector = get_selector_from_name(func_name).unwrap();
285
286 assert_eq!(selector, expected_selector);
287 }
288
289 #[test]
290 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
291 fn test_get_default_selector() {
292 let default_selector =
293 Felt::from_hex("0000000000000000000000000000000000000000000000000000000000000000")
294 .unwrap();
295
296 assert_eq!(
297 get_selector_from_name("__default__").unwrap(),
298 default_selector
299 );
300 assert_eq!(
301 get_selector_from_name("__l1_default__").unwrap(),
302 default_selector
303 );
304 }
305
306 #[test]
307 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
308 fn test_get_selector_from_non_ascii_name() {
309 let func_name = "🦀";
310
311 match get_selector_from_name(func_name) {
312 Err(_) => {}
313 _ => panic!("Should throw error on non-ASCII name"),
314 };
315 }
316
317 #[test]
318 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
319 fn test_get_storage_var_address() {
320 let var_name = "balance";
322 let expected_addr =
323 Felt::from_hex("0x0206f38f7e4f15e87567361213c28f235cccdaa1d7fd34c9db1dfe9489c6a091")
324 .unwrap();
325
326 let addr = get_storage_var_address(var_name, &[]).unwrap();
327
328 assert_eq!(addr, expected_addr);
329 }
330
331 #[test]
332 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
333 fn test_get_storage_var_address_with_args() {
334 let var_name = "balanceOf";
336 let expected_addr =
337 Felt::from_hex("0x07de334d65aa93d9185729b424025918b18892418c85b802775d1f0d2be30a1d")
338 .unwrap();
339
340 let addr = get_storage_var_address(var_name, &[1234u64.into()]).unwrap();
341
342 assert_eq!(addr, expected_addr);
343 }
344
345 #[test]
346 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
347 fn test_cairo_short_string_to_felt() {
348 let data = [
349 (
350 "abcdefghijklmnopqrstuvwxyz",
351 "156490583352162063278528710879425690470022892627113539022649722",
352 ),
353 (
354 "1234567890123456789012345678901",
355 "86921973946889608444641514252360676678984087116218318142845213717418291249",
356 ),
357 ];
358
359 for (str, felt_dec) in data {
360 assert_eq!(
361 cairo_short_string_to_felt(str).unwrap(),
362 Felt::from_dec_str(felt_dec).unwrap()
363 );
364 }
365 }
366
367 #[test]
368 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
369 fn test_cairo_short_string_to_felt_too_long() {
370 assert!(matches!(
371 cairo_short_string_to_felt("12345678901234567890123456789012"),
372 Err(CairoShortStringToFeltError::StringTooLong)
373 ));
374 }
375
376 #[test]
377 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
378 fn test_cairo_short_string_to_felt_non_ascii() {
379 assert!(matches!(
380 cairo_short_string_to_felt("🦀"),
381 Err(CairoShortStringToFeltError::NonAsciiCharacter)
382 ));
383 }
384
385 #[test]
386 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
387 fn test_parse_cairo_short_string() {
388 let data = [
389 (
390 "abcdefghijklmnopqrstuvwxyz",
391 "156490583352162063278528710879425690470022892627113539022649722",
392 ),
393 (
394 "1234567890123456789012345678901",
395 "86921973946889608444641514252360676678984087116218318142845213717418291249",
396 ),
397 ];
398
399 for (str, felt_dec) in data {
400 assert_eq!(
401 parse_cairo_short_string(&Felt::from_dec_str(felt_dec).unwrap()).unwrap(),
402 str
403 );
404 }
405 }
406
407 #[test]
408 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
409 fn test_parse_cairo_short_string_too_long() {
410 assert!(matches!(
411 parse_cairo_short_string(
412 &Felt::from_hex(
413 "0x0111111111111111111111111111111111111111111111111111111111111111"
414 )
415 .unwrap()
416 ),
417 Err(ParseCairoShortStringError::ValueOutOfRange)
418 ));
419 }
420
421 #[test]
422 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
423 fn test_parse_cairo_short_string_unexpected_null() {
424 assert!(matches!(
425 parse_cairo_short_string(
426 &Felt::from_hex(
427 "0x0011111111111111111111111111111111111111111111111111111111110011"
428 )
429 .unwrap()
430 ),
431 Err(ParseCairoShortStringError::UnexpectedNullTerminator)
432 ));
433 }
434
435 #[test]
436 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
437 fn test_get_contract_address() {
438 assert_eq!(
439 get_contract_address(
440 Felt::from_hex(
441 "0x0018a7a329d1d85b621350f2b5fc9c64b2e57dfe708525f0aff2c90de1e5b9c8"
442 )
443 .unwrap(),
444 Felt::from_hex(
445 "0x0750cd490a7cd1572411169eaa8be292325990d33c5d4733655fe6b926985062"
446 )
447 .unwrap(),
448 &[Felt::ONE],
449 Felt::ZERO
450 ),
451 Felt::from_hex("0x00da27ef7c3869c3a6cc6a0f7bf07a51c3e590825adba8a51cae27d815839eec")
452 .unwrap()
453 )
454 }
455
456 #[test]
457 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
458 fn test_udc_address_not_unique() {
459 let address = get_udc_deployed_address(
460 Felt::from_hex("0x06df0e9a9842d97ff3f4c6de7494d6e69d0a107a72150f9c53d59515b91ed9cb")
461 .unwrap(),
462 Felt::from_hex("0x0562fc1d911530d18a86ea3ef4be50018923898d3c573288c5abb9c2344459ed")
463 .unwrap(),
464 &UdcUniqueness::NotUnique,
465 &[Felt::from_hex("0x1234").unwrap()],
466 );
467
468 assert_eq!(
469 Felt::from_hex("0x0288e5952d2f2f0e897ea0c5401c6e9f584a89eebfb08b5b26f090a8bbf67eb6",)
470 .unwrap(),
471 address
472 );
473 }
474
475 #[test]
476 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
477 fn test_udc_address_unique() {
478 let address = get_udc_deployed_address(
479 Felt::from_hex("0x01f65976b95bf17ae1cb04afc9fc1eeee26d3e1aaa1f30aa535bf261e4322ab8")
480 .unwrap(),
481 Felt::from_hex("0x0562fc1d911530d18a86ea3ef4be50018923898d3c573288c5abb9c2344459ed")
482 .unwrap(),
483 &UdcUniqueness::Unique(UdcUniqueSettings {
484 deployer_address: Felt::from_hex(
485 "0x00b1461de04c6a1aa3375bdf9b7723a8779c082ffe21311d683a0b15c078b5dc",
486 )
487 .unwrap(),
488 udc_contract_address: Felt::from_hex(
489 "0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf",
490 )
491 .unwrap(),
492 }),
493 &[Felt::from_hex("0x1234").unwrap()],
494 );
495
496 assert_eq!(
497 Felt::from_hex("0x02406943b25942021f213b047c8765e531dddce3b981722f7aeb2ca137e18dbf",)
498 .unwrap(),
499 address
500 );
501 }
502}