cairo_native_runtime/
lib.rs

1#![allow(non_snake_case)]
2
3use cairo_lang_sierra_gas::core_libfunc_cost::{
4    DICT_SQUASH_REPEATED_ACCESS_COST, DICT_SQUASH_UNIQUE_KEY_COST,
5};
6use itertools::Itertools;
7use lazy_static::lazy_static;
8use num_traits::{ToPrimitive, Zero};
9use rand::Rng;
10use starknet_curve::curve_params::BETA;
11use starknet_types_core::{
12    curve::{AffinePoint, ProjectivePoint},
13    felt::Felt,
14    hash::StarkHash,
15};
16use std::{
17    alloc::{alloc, dealloc, realloc, Layout},
18    cell::Cell,
19    collections::{hash_map::Entry, HashMap},
20    ffi::{c_int, c_void},
21    fs::File,
22    io::Write,
23    mem::{forget, ManuallyDrop},
24    os::fd::FromRawFd,
25    ptr::{self, null, null_mut},
26    rc::Rc,
27    slice,
28};
29use std::{ops::Mul, vec::IntoIter};
30
31lazy_static! {
32    pub static ref HALF_PRIME: Felt = Felt::from_dec_str(
33        "1809251394333065606848661391547535052811553607665798349986546028067936010240"
34    )
35    .unwrap();
36    pub static ref DICT_GAS_REFUND_PER_ACCESS: u64 =
37        (DICT_SQUASH_UNIQUE_KEY_COST.cost() - DICT_SQUASH_REPEATED_ACCESS_COST.cost()) as u64;
38}
39
40#[no_mangle]
41#[allow(clippy::missing_safety_doc)]
42pub unsafe extern "C" fn cairo_native__get_version(target: *mut u8, length: usize) -> usize {
43    let version = env!("CARGO_PKG_VERSION");
44    assert!(length > version.len(), "version buffer not big enough");
45
46    let target = slice::from_raw_parts_mut(target, length);
47
48    target[..version.len()].copy_from_slice(version.as_bytes());
49    target[version.len()] = b'\0';
50
51    version.len()
52}
53
54/// Based on `cairo-lang-runner`'s implementation.
55///
56/// Source: <https://github.com/starkware-libs/cairo/blob/main/crates/cairo-lang-runner/src/casm_run/mod.rs#L1946-L1948>
57///
58/// # Safety
59///
60/// This function is intended to be called from MLIR, deals with pointers, and is therefore
61/// definitely unsafe to use manually.
62#[no_mangle]
63pub unsafe extern "C" fn cairo_native__libfunc__debug__print(
64    target_fd: i32,
65    data: *const [u8; 32],
66    len: u32,
67) -> i32 {
68    // Avoid closing `stdout` on all branches.
69    let mut target = ManuallyDrop::new(File::from_raw_fd(target_fd));
70
71    let mut items = Vec::with_capacity(len as usize);
72
73    for i in 0..len as usize {
74        let mut data = *data.add(i);
75        data[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
76
77        let value = Felt::from_bytes_le(&data);
78        items.push(value);
79    }
80
81    let value = format_for_debug(items.into_iter());
82
83    if write!(target, "{}", value).is_err() {
84        return 1;
85    };
86
87    0
88}
89
90/// Compute `pedersen(lhs, rhs)` and store it into `dst`.
91///
92/// All its operands need the values in big endian.
93///
94/// # Panics
95///
96/// This function will panic if either operand is out of range for a felt.
97///
98/// # Safety
99///
100/// This function is intended to be called from MLIR, deals with pointers, and is therefore
101/// definitely unsafe to use manually.
102#[no_mangle]
103pub unsafe extern "C" fn cairo_native__libfunc__pedersen(
104    dst: &mut [u8; 32],
105    lhs: &[u8; 32],
106    rhs: &[u8; 32],
107) {
108    // Extract arrays from the pointers.
109    let mut lhs = *lhs;
110    let mut rhs = *rhs;
111
112    lhs[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
113    rhs[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
114
115    // Convert to FieldElement.
116    let lhs = Felt::from_bytes_le(&lhs);
117    let rhs = Felt::from_bytes_le(&rhs);
118
119    // Compute pedersen hash and copy the result into `dst`.
120    let res = starknet_types_core::hash::Pedersen::hash(&lhs, &rhs);
121    *dst = res.to_bytes_le();
122}
123
124/// Compute `hades_permutation(op0, op1, op2)` and replace the operands with the results.
125///
126/// All operands need the values in big endian.
127///
128/// # Panics
129///
130/// This function will panic if either operand is out of range for a felt.
131///
132/// # Safety
133///
134/// This function is intended to be called from MLIR, deals with pointers, and is therefore
135/// definitely unsafe to use manually.
136#[no_mangle]
137pub unsafe extern "C" fn cairo_native__libfunc__hades_permutation(
138    op0: &mut [u8; 32],
139    op1: &mut [u8; 32],
140    op2: &mut [u8; 32],
141) {
142    op0[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
143    op1[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
144    op2[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
145
146    // Convert to FieldElement.
147    let mut state = [
148        Felt::from_bytes_le(op0),
149        Felt::from_bytes_le(op1),
150        Felt::from_bytes_le(op2),
151    ];
152
153    // Compute Poseidon permutation.
154    starknet_types_core::hash::Poseidon::hades_permutation(&mut state);
155
156    // Write back the results.
157    *op0 = state[0].to_bytes_le();
158    *op1 = state[1].to_bytes_le();
159    *op2 = state[2].to_bytes_le();
160}
161
162/// Felt252 type used in cairo native runtime
163#[derive(Debug)]
164pub struct FeltDict {
165    pub mappings: HashMap<[u8; 32], usize>,
166
167    pub layout: Layout,
168    pub elements: *mut (),
169
170    pub dup_fn: Option<extern "C" fn(*mut c_void, *mut c_void)>,
171    pub drop_fn: Option<extern "C" fn(*mut c_void)>,
172
173    pub count: u64,
174}
175
176impl Clone for FeltDict {
177    fn clone(&self) -> Self {
178        let mut new_dict = FeltDict {
179            mappings: HashMap::with_capacity(self.mappings.len()),
180
181            layout: self.layout,
182            elements: if self.mappings.is_empty() {
183                null_mut()
184            } else {
185                unsafe {
186                    alloc(Layout::from_size_align_unchecked(
187                        self.layout.pad_to_align().size() * self.mappings.len(),
188                        self.layout.align(),
189                    ))
190                    .cast()
191                }
192            },
193
194            dup_fn: self.dup_fn,
195            drop_fn: self.drop_fn,
196
197            // TODO: Check if `0` is fine or otherwise we should copy the value from `old_dict` too.
198            count: 0,
199        };
200
201        for (&key, &old_index) in self.mappings.iter() {
202            let old_value_ptr = unsafe {
203                self.elements
204                    .byte_add(self.layout.pad_to_align().size() * old_index)
205            };
206
207            let new_index = new_dict.mappings.len();
208            let new_value_ptr = unsafe {
209                new_dict
210                    .elements
211                    .byte_add(new_dict.layout.pad_to_align().size() * new_index)
212            };
213
214            new_dict.mappings.insert(key, new_index);
215            match self.dup_fn {
216                Some(dup_fn) => dup_fn(old_value_ptr.cast(), new_value_ptr.cast()),
217                None => unsafe {
218                    ptr::copy_nonoverlapping::<u8>(
219                        old_value_ptr.cast(),
220                        new_value_ptr.cast(),
221                        self.layout.size(),
222                    )
223                },
224            }
225        }
226
227        new_dict
228    }
229}
230
231impl Drop for FeltDict {
232    fn drop(&mut self) {
233        // Free the entries manually.
234        if let Some(drop_fn) = self.drop_fn {
235            for (_, &index) in self.mappings.iter() {
236                let value_ptr = unsafe {
237                    self.elements
238                        .byte_add(self.layout.pad_to_align().size() * index)
239                };
240
241                drop_fn(value_ptr.cast());
242            }
243        }
244
245        // Free the value data.
246        if !self.elements.is_null() {
247            unsafe {
248                dealloc(
249                    self.elements.cast(),
250                    Layout::from_size_align_unchecked(
251                        self.layout.pad_to_align().size() * self.mappings.capacity(),
252                        self.layout.align(),
253                    ),
254                )
255            };
256        }
257    }
258}
259
260/// Allocate a new dictionary.
261///
262/// # Safety
263///
264/// This function is intended to be called from MLIR, deals with pointers, and is therefore
265/// definitely unsafe to use manually.
266#[no_mangle]
267pub unsafe extern "C" fn cairo_native__dict_new(
268    size: u64,
269    align: u64,
270    dup_fn: Option<extern "C" fn(*mut c_void, *mut c_void)>,
271    drop_fn: Option<extern "C" fn(*mut c_void)>,
272) -> *const FeltDict {
273    Rc::into_raw(Rc::new(FeltDict {
274        mappings: HashMap::default(),
275
276        layout: Layout::from_size_align_unchecked(size as usize, align as usize),
277        elements: null_mut(),
278
279        dup_fn,
280        drop_fn,
281
282        count: 0,
283    }))
284}
285
286/// Free a dictionary using an optional callback to drop each element.
287///
288/// # Safety
289///
290/// This function is intended to be called from MLIR, deals with pointers, and is therefore
291/// definitely unsafe to use manually.
292// Note: Using `Option<extern "C" fn(*mut c_void)>` is ffi-safe thanks to Option's null
293//   pointer optimization. Check out
294//   https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization for more info.
295#[no_mangle]
296pub unsafe extern "C" fn cairo_native__dict_drop(ptr: *const FeltDict) {
297    drop(Rc::from_raw(ptr));
298}
299
300/// Duplicate a dictionary using a provided callback to clone each element.
301///
302/// # Safety
303///
304/// This function is intended to be called from MLIR, deals with pointers, and is therefore
305/// definitely unsafe to use manually.
306#[no_mangle]
307pub unsafe extern "C" fn cairo_native__dict_dup(dict_ptr: *const FeltDict) -> *const FeltDict {
308    let old_dict = Rc::from_raw(dict_ptr);
309    let new_dict = Rc::clone(&old_dict);
310
311    forget(old_dict);
312    Rc::into_raw(new_dict)
313}
314
315/// Return a pointer to the entry's value pointer for a given key, inserting a null pointer if not
316/// present. Increment the access count.
317///
318/// The null pointer will be either updated by `felt252_dict_entry_finalize` or removed (along with
319/// everything else in the dict) by the entry's drop implementation.
320///
321/// # Safety
322///
323/// This function is intended to be called from MLIR, deals with pointers, and is therefore
324/// definitely unsafe to use manually.
325#[no_mangle]
326pub unsafe extern "C" fn cairo_native__dict_get(
327    dict: *const FeltDict,
328    key: &[u8; 32],
329    value_ptr: *mut *mut c_void,
330) -> c_int {
331    let mut dict_rc = Rc::from_raw(dict);
332    let dict = Rc::make_mut(&mut dict_rc);
333
334    let num_mappings = dict.mappings.len();
335    let has_capacity = num_mappings != dict.mappings.capacity();
336
337    let (is_present, index) = match dict.mappings.entry(*key) {
338        Entry::Occupied(entry) => (true, *entry.get()),
339        Entry::Vacant(entry) => {
340            entry.insert(num_mappings);
341            (false, num_mappings)
342        }
343    };
344
345    // Maybe realloc (conditions: !has_capacity && !is_present).
346    if !has_capacity && !is_present {
347        dict.elements = realloc(
348            dict.elements.cast(),
349            Layout::from_size_align_unchecked(
350                dict.layout.pad_to_align().size() * dict.mappings.len(),
351                dict.layout.align(),
352            ),
353            dict.layout.pad_to_align().size() * dict.mappings.capacity(),
354        )
355        .cast();
356    }
357
358    *value_ptr = dict
359        .elements
360        .byte_add(dict.layout.pad_to_align().size() * index)
361        .cast();
362
363    dict.count += 1;
364    forget(dict_rc);
365
366    is_present as c_int
367}
368
369/// Compute the total gas refund for the dictionary at squash time.
370///
371/// # Safety
372///
373/// This function is intended to be called from MLIR, deals with pointers, and is therefore
374/// definitely unsafe to use manually.
375#[no_mangle]
376pub unsafe extern "C" fn cairo_native__dict_gas_refund(ptr: *const FeltDict) -> u64 {
377    let dict = Rc::from_raw(ptr);
378    let amount =
379        (dict.count.saturating_sub(dict.mappings.len() as u64)) * *DICT_GAS_REFUND_PER_ACCESS;
380
381    forget(dict);
382    amount
383}
384
385/// Compute `ec_point_from_x_nz(x)` and store it.
386///
387/// # Panics
388///
389/// This function will panic if either operand is out of range for a felt.
390///
391/// # Safety
392///
393/// This function is intended to be called from MLIR, deals with pointers, and is therefore
394/// definitely unsafe to use manually.
395#[no_mangle]
396pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_point_from_x_nz(
397    point_ptr: &mut [[u8; 32]; 2],
398) -> bool {
399    point_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
400    let x = Felt::from_bytes_le(&point_ptr[0]);
401
402    // https://github.com/starkware-libs/cairo/blob/aaad921bba52e729dc24ece07fab2edf09ccfa15/crates/cairo-lang-sierra-to-casm/src/invocations/ec.rs#L63
403
404    let x2 = x * x;
405    let x3 = x2 * x;
406    let alpha_x_plus_beta = x + BETA;
407    let rhs = x3 + alpha_x_plus_beta;
408    // https://github.com/starkware-libs/cairo/blob/9b603b88c2e5a98eec1bb8f323260b7765e94911/crates/cairo-lang-runner/src/casm_run/mod.rs#L1825
409    let y = rhs
410        .sqrt()
411        .unwrap_or_else(|| (Felt::THREE * rhs).sqrt().unwrap());
412    let y = y.min(-y);
413
414    match AffinePoint::new(x, y) {
415        Ok(point) => {
416            point_ptr[1] = point.y().to_bytes_le();
417            true
418        }
419        Err(_) => false,
420    }
421}
422
423/// Compute `ec_point_try_new_nz(x)`.
424///
425/// # Panics
426///
427/// This function will panic if either operand is out of range for a felt.
428///
429/// # Safety
430///
431/// This function is intended to be called from MLIR, deals with pointers, and is therefore
432/// definitely unsafe to use manually.
433#[no_mangle]
434pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_point_try_new_nz(
435    point_ptr: &mut [[u8; 32]; 2],
436) -> bool {
437    point_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
438    point_ptr[1][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
439
440    let x = Felt::from_bytes_le(&point_ptr[0]);
441    let y = Felt::from_bytes_le(&point_ptr[1]);
442
443    match AffinePoint::new(x, y) {
444        Ok(point) => {
445            point_ptr[0] = point.x().to_bytes_le();
446            point_ptr[1] = point.y().to_bytes_le();
447            true
448        }
449        Err(_) => false,
450    }
451}
452
453/// Compute `ec_state_init()` and store the state back.
454///
455/// # Safety
456///
457/// This function is intended to be called from MLIR, deals with pointers, and is therefore
458/// definitely unsafe to use manually.
459#[no_mangle]
460pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_init(state_ptr: &mut [[u8; 32]; 4]) {
461    // https://github.com/starkware-libs/cairo/blob/aaad921bba52e729dc24ece07fab2edf09ccfa15/crates/cairo-lang-runner/src/casm_run/mod.rs#L1802
462    let mut rng = rand::thread_rng();
463    let (random_x, random_y) = loop {
464        // Randominzing 31 bytes to make sure is in range.
465        let x_bytes: [u8; 31] = rng.gen();
466        let random_x = Felt::from_bytes_be_slice(&x_bytes);
467        let random_y_squared = random_x * random_x * random_x + random_x + BETA;
468        if let Some(random_y) = random_y_squared.sqrt() {
469            break (random_x, random_y);
470        }
471    };
472
473    // We already made sure its a valid point.
474    let state = AffinePoint::new_unchecked(random_x, random_y);
475
476    state_ptr[0] = state.x().to_bytes_le();
477    state_ptr[1] = state.y().to_bytes_le();
478    state_ptr[2] = state_ptr[0];
479    state_ptr[3] = state_ptr[1];
480}
481
482/// Compute `ec_state_add(state, point)` and store the state back.
483///
484/// # Panics
485///
486/// This function will panic if either operand is out of range for a felt.
487///
488/// # Safety
489///
490/// This function is intended to be called from MLIR, deals with pointers, and is therefore
491/// definitely unsafe to use manually.
492#[no_mangle]
493pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_add(
494    state_ptr: &mut [[u8; 32]; 4],
495    point_ptr: &[[u8; 32]; 2],
496) {
497    state_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
498    state_ptr[1][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
499
500    let mut point_ptr = *point_ptr;
501    point_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
502    point_ptr[1][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
503
504    // We use unchecked methods because the inputs must already be valid points.
505    let mut state = ProjectivePoint::from_affine_unchecked(
506        Felt::from_bytes_le(&state_ptr[0]),
507        Felt::from_bytes_le(&state_ptr[1]),
508    );
509    let point = AffinePoint::new_unchecked(
510        Felt::from_bytes_le(&point_ptr[0]),
511        Felt::from_bytes_le(&point_ptr[1]),
512    );
513
514    state += &point;
515    let state = state.to_affine().unwrap();
516
517    state_ptr[0] = state.x().to_bytes_le();
518    state_ptr[1] = state.y().to_bytes_le();
519}
520
521/// Compute `ec_state_add_mul(state, scalar, point)` and store the state back.
522///
523/// # Panics
524///
525/// This function will panic if either operand is out of range for a felt.
526///
527/// # Safety
528///
529/// This function is intended to be called from MLIR, deals with pointers, and is therefore
530/// definitely unsafe to use manually.
531#[no_mangle]
532pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_add_mul(
533    state_ptr: &mut [[u8; 32]; 4],
534    scalar_ptr: &[u8; 32],
535    point_ptr: &[[u8; 32]; 2],
536) {
537    state_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
538    state_ptr[1][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
539
540    let mut point_ptr = *point_ptr;
541    point_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
542    point_ptr[1][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
543
544    let mut scalar_ptr = *scalar_ptr;
545    scalar_ptr[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
546
547    // Here the points should already be checked as valid, so we can use unchecked.
548    let mut state = ProjectivePoint::from_affine_unchecked(
549        Felt::from_bytes_le(&state_ptr[0]),
550        Felt::from_bytes_le(&state_ptr[1]),
551    );
552    let point = ProjectivePoint::from_affine_unchecked(
553        Felt::from_bytes_le(&point_ptr[0]),
554        Felt::from_bytes_le(&point_ptr[1]),
555    );
556    let scalar = Felt::from_bytes_le(&scalar_ptr);
557
558    state += &point.mul(scalar);
559    let state = state.to_affine().unwrap();
560
561    state_ptr[0] = state.x().to_bytes_le();
562    state_ptr[1] = state.y().to_bytes_le();
563}
564
565/// Compute `ec_state_try_finalize_nz(state)` and store the result.
566///
567/// # Panics
568///
569/// This function will panic if either operand is out of range for a felt.
570///
571/// # Safety
572///
573/// This function is intended to be called from MLIR, deals with pointers, and is therefore
574/// definitely unsafe to use manually.
575#[no_mangle]
576pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_try_finalize_nz(
577    point_ptr: &mut [[u8; 32]; 2],
578    state_ptr: &[[u8; 32]; 4],
579) -> bool {
580    let mut state_ptr = *state_ptr;
581    state_ptr[0][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
582    state_ptr[1][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
583    state_ptr[2][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
584    state_ptr[3][31] &= 0x0F; // Filter out first 4 bits (they're outside an i252).
585
586    // We use unchecked methods because the inputs must already be valid points.
587    let state = ProjectivePoint::from_affine_unchecked(
588        Felt::from_bytes_le(&state_ptr[0]),
589        Felt::from_bytes_le(&state_ptr[1]),
590    );
591    let random = ProjectivePoint::from_affine_unchecked(
592        Felt::from_bytes_le(&state_ptr[2]),
593        Felt::from_bytes_le(&state_ptr[3]),
594    );
595
596    if state.x() == random.x() && state.y() == random.y() {
597        false
598    } else {
599        let point = &state - &random;
600        let point = point.to_affine().unwrap();
601
602        point_ptr[0] = point.x().to_bytes_le();
603        point_ptr[1] = point.y().to_bytes_le();
604
605        true
606    }
607}
608
609thread_local! {
610    // We can use cell because a ptr is copy.
611    static BUILTIN_COSTS: Cell<*const u64> = const {
612        Cell::new(null())
613    };
614}
615
616/// Store the gas builtin in the internal thread local. Returns the old pointer, to restore it after execution.
617/// Not a runtime metadata method, it should be called before the program is executed.
618#[no_mangle]
619pub extern "C" fn cairo_native__set_costs_builtin(ptr: *const u64) -> *const u64 {
620    let old = BUILTIN_COSTS.get();
621    BUILTIN_COSTS.set(ptr);
622    old
623}
624
625/// Get the gas builtin from the internal thread local.
626#[no_mangle]
627pub extern "C" fn cairo_native__get_costs_builtin() -> *const u64 {
628    if BUILTIN_COSTS.get().is_null() {
629        // We shouldn't panic here, but we can print a big message.
630        eprintln!("BUILTIN_COSTS POINTER IS NULL!");
631    }
632    BUILTIN_COSTS.get()
633}
634
635// Utility methods for the print runtime function
636
637/// Formats the given felts as a debug string.
638fn format_for_debug(mut felts: IntoIter<Felt>) -> String {
639    let mut items = Vec::new();
640    while let Some(item) = format_next_item(&mut felts) {
641        items.push(item);
642    }
643    if let [item] = &items[..] {
644        if item.is_string {
645            return item.item.clone();
646        }
647    }
648    items
649        .into_iter()
650        .map(|item| {
651            if item.is_string {
652                format!("{}\n", item.item)
653            } else {
654                format!("[DEBUG]\t{}\n", item.item)
655            }
656        })
657        .join("")
658}
659
660/// A formatted string representation of anything formattable (e.g. ByteArray, felt, short-string).
661pub struct FormattedItem {
662    /// The formatted string representing the item.
663    item: String,
664    /// Whether the item is a string.
665    is_string: bool,
666}
667impl FormattedItem {
668    /// Returns the formatted item as is.
669    #[must_use]
670    pub fn get(self) -> String {
671        self.item
672    }
673    /// Wraps the formatted item with quote, if it's a string. Otherwise returns it as is.
674    #[must_use]
675    pub fn quote_if_string(self) -> String {
676        if self.is_string {
677            format!("\"{}\"", self.item)
678        } else {
679            self.item
680        }
681    }
682}
683
684pub const BYTE_ARRAY_MAGIC: &str =
685    "46a6158a16a947e5916b2a2ca68501a45e93d7110e81aa2d6438b1c57c879a3";
686pub const BYTES_IN_WORD: usize = 31;
687
688/// Formats a string or a short string / `felt252`. Returns the formatted string and a boolean
689/// indicating whether it's a string. If can't format the item, returns None.
690pub fn format_next_item<T>(values: &mut T) -> Option<FormattedItem>
691where
692    T: Iterator<Item = Felt> + Clone,
693{
694    let first_felt = values.next()?;
695
696    if first_felt == Felt::from_hex(BYTE_ARRAY_MAGIC).unwrap() {
697        if let Some(string) = try_format_string(values) {
698            return Some(FormattedItem {
699                item: string,
700                is_string: true,
701            });
702        }
703    }
704    Some(FormattedItem {
705        item: format_short_string(&first_felt),
706        is_string: false,
707    })
708}
709
710/// Formats a `Felt252`, as a short string if possible.
711fn format_short_string(value: &Felt) -> String {
712    let hex_value = value.to_biguint();
713    match as_cairo_short_string(value) {
714        Some(as_string) => format!("{hex_value:#x} ('{as_string}')"),
715        None => format!("{hex_value:#x}"),
716    }
717}
718
719/// Tries to format a string, represented as a sequence of `Felt252`s.
720/// If the sequence is not a valid serialization of a `ByteArray`, returns None and doesn't change the
721/// given iterator (`values`).
722fn try_format_string<T>(values: &mut T) -> Option<String>
723where
724    T: Iterator<Item = Felt> + Clone,
725{
726    // Clone the iterator and work with the clone. If the extraction of the string is successful,
727    // change the original iterator to the one we worked with. If not, continue with the
728    // original iterator at the original point.
729    let mut cloned_values_iter = values.clone();
730
731    let num_full_words = cloned_values_iter.next()?.to_usize()?;
732    let full_words = cloned_values_iter
733        .by_ref()
734        .take(num_full_words)
735        .collect_vec();
736    let pending_word = cloned_values_iter.next()?;
737    let pending_word_len = cloned_values_iter.next()?.to_usize()?;
738
739    let full_words_string = full_words
740        .into_iter()
741        .map(|word| as_cairo_short_string_ex(&word, BYTES_IN_WORD))
742        .collect::<Option<Vec<String>>>()?
743        .join("");
744    let pending_word_string = as_cairo_short_string_ex(&pending_word, pending_word_len)?;
745
746    // Extraction was successful, change the original iterator to the one we worked with.
747    *values = cloned_values_iter;
748
749    Some(format!("{full_words_string}{pending_word_string}"))
750}
751
752/// Converts a bigint representing a felt252 to a Cairo short-string.
753#[must_use]
754pub fn as_cairo_short_string(value: &Felt) -> Option<String> {
755    let mut as_string = String::default();
756    let mut is_end = false;
757    for byte in value.to_biguint().to_bytes_be() {
758        if byte == 0 {
759            is_end = true;
760        } else if is_end {
761            return None;
762        } else if byte.is_ascii_graphic() || byte.is_ascii_whitespace() {
763            as_string.push(byte as char);
764        } else {
765            return None;
766        }
767    }
768    Some(as_string)
769}
770
771/// Converts a bigint representing a felt252 to a Cairo short-string of the given length.
772/// Nulls are allowed and length must be <= 31.
773#[must_use]
774pub fn as_cairo_short_string_ex(value: &Felt, length: usize) -> Option<String> {
775    if length == 0 {
776        return if value.is_zero() {
777            Some(String::new())
778        } else {
779            None
780        };
781    }
782    if length > 31 {
783        // A short string can't be longer than 31 bytes.
784        return None;
785    }
786
787    // We pass through biguint as felt252.to_bytes_be() does not trim leading zeros.
788    let bytes = value.to_biguint().to_bytes_be();
789    let bytes_len = bytes.len();
790    if bytes_len > length {
791        // `value` has more bytes than expected.
792        return None;
793    }
794
795    let mut as_string = String::new();
796    for byte in bytes {
797        if byte == 0 {
798            as_string.push_str(r"\0");
799        } else if byte.is_ascii_graphic() || byte.is_ascii_whitespace() {
800            as_string.push(byte as char);
801        } else {
802            as_string.push_str(format!(r"\x{:02x}", byte).as_str());
803        }
804    }
805
806    // `to_bytes_be` misses starting nulls. Prepend them as needed.
807    let missing_nulls = length - bytes_len;
808    as_string.insert_str(0, &r"\0".repeat(missing_nulls));
809
810    Some(as_string)
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816    use std::{
817        env, fs,
818        io::{Read, Seek},
819        os::fd::AsRawFd,
820    };
821
822    pub fn felt252_short_str(value: &str) -> Felt {
823        let values: Vec<_> = value
824            .chars()
825            .filter_map(|c| c.is_ascii().then_some(c as u8))
826            .collect();
827
828        assert!(values.len() < 32);
829        Felt::from_bytes_be_slice(&values)
830    }
831
832    #[test]
833    fn test_debug_print() {
834        let dir = env::temp_dir();
835        fs::remove_file(dir.join("print.txt")).ok();
836        let mut file = File::create_new(dir.join("print.txt")).unwrap();
837        {
838            let fd = file.as_raw_fd();
839            let data = felt252_short_str("hello world");
840            let data = data.to_bytes_le();
841            unsafe { cairo_native__libfunc__debug__print(fd, &data, 1) };
842        }
843        file.seek(std::io::SeekFrom::Start(0)).unwrap();
844
845        let mut result = String::new();
846        file.read_to_string(&mut result).unwrap();
847
848        assert_eq!(
849            result,
850            "[DEBUG]\t0x68656c6c6f20776f726c64 ('hello world')\n"
851        );
852    }
853
854    #[test]
855    fn test_pederesen() {
856        let mut dst = [0; 32];
857        let lhs = Felt::from(1).to_bytes_le();
858        let rhs = Felt::from(3).to_bytes_le();
859
860        unsafe {
861            cairo_native__libfunc__pedersen(&mut dst, &lhs, &rhs);
862        }
863
864        assert_eq!(
865            dst,
866            [
867                84, 98, 174, 134, 3, 124, 237, 179, 166, 110, 159, 98, 170, 35, 83, 237, 130, 154,
868                236, 0, 205, 134, 200, 185, 39, 92, 0, 228, 132, 217, 130, 5
869            ]
870        )
871    }
872
873    #[test]
874    fn test_hades_permutation() {
875        let mut op0 = Felt::from(1).to_bytes_le();
876        let mut op1 = Felt::from(1).to_bytes_le();
877        let mut op2 = Felt::from(1).to_bytes_le();
878
879        unsafe {
880            cairo_native__libfunc__hades_permutation(&mut op0, &mut op1, &mut op2);
881        }
882
883        assert_eq!(
884            Felt::from_bytes_le(&op0),
885            Felt::from_hex("0x4ebdde1149fcacbb41e4fc342432a48c97994fd045f432ad234ae9279269779")
886                .unwrap()
887        );
888        assert_eq!(
889            Felt::from_bytes_le(&op1),
890            Felt::from_hex("0x7f4cec57dd08b69414f7de7dffa230fc90fa3993673c422408af05831e0cc98")
891                .unwrap()
892        );
893        assert_eq!(
894            Felt::from_bytes_le(&op2),
895            Felt::from_hex("0x5b5d00fd09caade43caffe70527fa84d5d9cd51e22c2ce115693ecbb5854d6a")
896                .unwrap()
897        );
898    }
899
900    #[test]
901    fn test_dict() {
902        let dict = unsafe {
903            cairo_native__dict_new(
904                size_of::<u64>() as u64,
905                align_of::<u64>() as u64,
906                None,
907                None,
908            )
909        };
910
911        let key = Felt::ONE.to_bytes_le();
912        let mut ptr = null_mut::<u64>();
913
914        assert_eq!(
915            unsafe { cairo_native__dict_get(dict, &key, (&raw mut ptr).cast()) },
916            0,
917        );
918        assert!(!ptr.is_null());
919        unsafe { *ptr = 24 };
920
921        assert_eq!(
922            unsafe { cairo_native__dict_get(dict, &key, (&raw mut ptr).cast()) },
923            1,
924        );
925        assert!(!ptr.is_null());
926        assert_eq!(unsafe { *ptr }, 24);
927        unsafe { *ptr = 42 };
928
929        let refund = unsafe { cairo_native__dict_gas_refund(dict) };
930        assert_eq!(refund, 4050);
931
932        let cloned_dict = unsafe { cairo_native__dict_dup(&*dict) };
933        unsafe { cairo_native__dict_drop(dict) };
934
935        assert_eq!(
936            unsafe { cairo_native__dict_get(cloned_dict, &key, (&raw mut ptr).cast()) },
937            1,
938        );
939        assert!(!ptr.is_null());
940        assert_eq!(unsafe { *ptr }, 42);
941
942        unsafe { cairo_native__dict_drop(cloned_dict) };
943    }
944
945    #[test]
946    fn test_ec__ec_point() {
947        let mut state = [
948            Felt::ZERO.to_bytes_le(),
949            Felt::ZERO.to_bytes_le(),
950            Felt::ZERO.to_bytes_le(),
951            Felt::ZERO.to_bytes_le(),
952        ];
953
954        unsafe { cairo_native__libfunc__ec__ec_state_init(&mut state) };
955
956        let points: &mut [[u8; 32]; 2] = (&mut state[..2]).try_into().unwrap();
957
958        let result = unsafe { cairo_native__libfunc__ec__ec_point_try_new_nz(points) };
959
960        // point should be valid since it was made with state init
961        assert!(result);
962    }
963
964    #[test]
965    fn test_ec__ec_point_add() {
966        // Test values taken from starknet-rs
967        let mut state = [
968            Felt::from_dec_str(
969                "874739451078007766457464989774322083649278607533249481151382481072868806602",
970            )
971            .unwrap()
972            .to_bytes_le(),
973            Felt::from_dec_str(
974                "152666792071518830868575557812948353041420400780739481342941381225525861407",
975            )
976            .unwrap()
977            .to_bytes_le(),
978            Felt::from_dec_str(
979                "874739451078007766457464989774322083649278607533249481151382481072868806602",
980            )
981            .unwrap()
982            .to_bytes_le(),
983            Felt::from_dec_str(
984                "152666792071518830868575557812948353041420400780739481342941381225525861407",
985            )
986            .unwrap()
987            .to_bytes_le(),
988        ];
989
990        let point = [
991            Felt::from_dec_str(
992                "874739451078007766457464989774322083649278607533249481151382481072868806602",
993            )
994            .unwrap()
995            .to_bytes_le(),
996            Felt::from_dec_str(
997                "152666792071518830868575557812948353041420400780739481342941381225525861407",
998            )
999            .unwrap()
1000            .to_bytes_le(),
1001        ];
1002
1003        unsafe {
1004            cairo_native__libfunc__ec__ec_state_add(&mut state, &point);
1005        };
1006
1007        assert_eq!(
1008            state[0],
1009            Felt::from_dec_str(
1010                "3324833730090626974525872402899302150520188025637965566623476530814354734325",
1011            )
1012            .unwrap()
1013            .to_bytes_le()
1014        );
1015        assert_eq!(
1016            state[1],
1017            Felt::from_dec_str(
1018                "3147007486456030910661996439995670279305852583596209647900952752170983517249",
1019            )
1020            .unwrap()
1021            .to_bytes_le()
1022        );
1023    }
1024}