Skip to content

Commit

Permalink
Add runtime tests (#857)
Browse files Browse the repository at this point in the history
* runtime tests

* dict tests

* improve dict test

* refund

* tests

* fix runtime desc

* remove old comment

* manually drop target fd

* avoid possible overflow

* fix typo
  • Loading branch information
edg-l authored Oct 18, 2024
1 parent 0e251c5 commit 530d1e9
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 11 deletions.
7 changes: 5 additions & 2 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
[package]
name = "cairo-native-runtime"
version = "0.2.0-alpha.4"
description = "A compiler to convert Cairo's intermediate representation Sierra code to MLIR."
description = "The runtime for cairo-native."
edition = "2021"
license = "Apache-2.0"
keywords = ["starknet", "cairo", "runtime"]

[lib]
crate-type = ["rlib", "cdylib", "staticlib"]

[dependencies]
starknet-types-core = { version = "0.1.7", default-features = false, features = [
"std", "serde", "hash"
"std",
"serde",
"hash",
] }
cairo-lang-sierra-gas = "2.8.4"
starknet-curve = "0.5.1"
Expand Down
242 changes: 236 additions & 6 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use starknet_types_core::{
felt::Felt,
hash::StarkHash,
};
use std::{collections::HashMap, ffi::c_void, fs::File, io::Write, os::fd::FromRawFd};
use std::{
collections::HashMap, ffi::c_void, fs::File, io::Write, mem::ManuallyDrop, os::fd::FromRawFd,
};
use std::{ops::Mul, vec::IntoIter};

lazy_static! {
Expand All @@ -39,7 +41,8 @@ pub unsafe extern "C" fn cairo_native__libfunc__debug__print(
data: *const [u8; 32],
len: u32,
) -> i32 {
let mut target = File::from_raw_fd(target_fd);
// Avoid closing `stdout` on all branches.
let mut target = ManuallyDrop::new(File::from_raw_fd(target_fd));

let mut items = Vec::with_capacity(len as usize);

Expand All @@ -57,9 +60,6 @@ pub unsafe extern "C" fn cairo_native__libfunc__debug__print(
return 1;
};

// Avoid closing `stdout`.
std::mem::forget(target);

0
}

Expand Down Expand Up @@ -248,7 +248,7 @@ pub unsafe extern "C" fn cairo_native__dict_get(
#[no_mangle]
pub unsafe extern "C" fn cairo_native__dict_gas_refund(ptr: *const FeltDict) -> u64 {
let dict = &*ptr;
(dict.count - dict.inner.len() as u64) * *DICT_GAS_REFUND_PER_ACCESS
(dict.count.saturating_sub(dict.inner.len() as u64)) * *DICT_GAS_REFUND_PER_ACCESS
}

/// Compute `ec_point_from_x_nz(x)` and store it.
Expand Down Expand Up @@ -648,3 +648,233 @@ pub fn as_cairo_short_string_ex(value: &Felt, length: usize) -> Option<String> {

Some(as_string)
}

#[cfg(test)]
mod tests {
use std::{
env,
fs::{remove_file, File},
io::{Read, Seek},
os::{fd::AsRawFd, raw::c_void},
};

use starknet_types_core::felt::Felt;

use crate::{
cairo_native__dict_drop, cairo_native__dict_dup, cairo_native__dict_gas_refund,
cairo_native__dict_get, cairo_native__dict_new, cairo_native__libfunc__debug__print,
cairo_native__libfunc__ec__ec_point_try_new_nz, cairo_native__libfunc__ec__ec_state_add,
cairo_native__libfunc__ec__ec_state_init, cairo_native__libfunc__hades_permutation,
cairo_native__libfunc__pedersen,
};

pub fn felt252_short_str(value: &str) -> Felt {
let values: Vec<_> = value
.chars()
.filter_map(|c| c.is_ascii().then_some(c as u8))
.collect();

assert!(values.len() < 32);
Felt::from_bytes_be_slice(&values)
}

#[test]
fn test_debug_print() {
let dir = env::temp_dir();
remove_file(dir.join("print.txt")).ok();
let mut file = File::create_new(dir.join("print.txt")).unwrap();
{
let fd = file.as_raw_fd();
let data = felt252_short_str("hello world");
let data = data.to_bytes_le();
unsafe { cairo_native__libfunc__debug__print(fd, &data, 1) };
}
file.seek(std::io::SeekFrom::Start(0)).unwrap();

let mut result = String::new();
file.read_to_string(&mut result).unwrap();

assert_eq!(
result,
"[DEBUG]\t0x68656c6c6f20776f726c64 ('hello world')\n"
);
}

#[test]
fn test_pederesen() {
let mut dst = [0; 32];
let lhs = Felt::from(1).to_bytes_le();
let rhs = Felt::from(3).to_bytes_le();

unsafe {
cairo_native__libfunc__pedersen(&mut dst, &lhs, &rhs);
}

assert_eq!(
dst,
[
84, 98, 174, 134, 3, 124, 237, 179, 166, 110, 159, 98, 170, 35, 83, 237, 130, 154,
236, 0, 205, 134, 200, 185, 39, 92, 0, 228, 132, 217, 130, 5
]
)
}

#[test]
fn test_hades_permutation() {
let mut op0 = Felt::from(1).to_bytes_le();
let mut op1 = Felt::from(1).to_bytes_le();
let mut op2 = Felt::from(1).to_bytes_le();

unsafe {
cairo_native__libfunc__hades_permutation(&mut op0, &mut op1, &mut op2);
}

assert_eq!(
Felt::from_bytes_le(&op0),
Felt::from_hex("0x4ebdde1149fcacbb41e4fc342432a48c97994fd045f432ad234ae9279269779")
.unwrap()
);
assert_eq!(
Felt::from_bytes_le(&op1),
Felt::from_hex("0x7f4cec57dd08b69414f7de7dffa230fc90fa3993673c422408af05831e0cc98")
.unwrap()
);
assert_eq!(
Felt::from_bytes_le(&op2),
Felt::from_hex("0x5b5d00fd09caade43caffe70527fa84d5d9cd51e22c2ce115693ecbb5854d6a")
.unwrap()
);
}

// Test free_fn for the dict testing, values are u64.
pub extern "C" fn free_fn_test(ptr: *mut c_void) {
assert!(!ptr.is_null());
let b: Box<u64> = unsafe { Box::from_raw(ptr.cast()) };
drop(b);
}

pub extern "C" fn dup_fn_test(ptr: *mut c_void) -> *mut c_void {
assert!(!ptr.is_null());
let ptr: *mut u64 = ptr.cast();
let dup = unsafe { Box::into_raw(Box::new(*ptr)) };
dup.cast()
}

#[test]
fn test_dict() {
let dict = unsafe { cairo_native__dict_new(free_fn_test) };

let key = Felt::ONE.to_bytes_le();

{
let ptr: *mut *mut u64 = unsafe { cairo_native__dict_get(&mut *dict, &key) }.cast();
unsafe { *ptr = Box::into_raw(Box::new(2u64)) };
}

{
let ptr: *mut *mut u64 = unsafe { cairo_native__dict_get(&mut *dict, &key) }.cast();
assert!(!ptr.is_null());
assert!(!unsafe { *ptr }.is_null());
assert_eq!(unsafe { **ptr }, 2);
}

{
let refund = unsafe { cairo_native__dict_gas_refund(dict) };
assert_eq!(refund, 4050);
}

let cloned_dict = unsafe { cairo_native__dict_dup(dict, dup_fn_test) };

unsafe { cairo_native__dict_drop(dict, None) };

{
let ptr: *mut *mut u64 =
unsafe { cairo_native__dict_get(&mut *cloned_dict, &key) }.cast();
assert!(!ptr.is_null());
assert!(!unsafe { *ptr }.is_null());
assert_eq!(unsafe { **ptr }, 2);
}

unsafe { cairo_native__dict_drop(cloned_dict, None) };
}

#[test]
fn test_ec__ec_point() {
let mut state = [
Felt::ZERO.to_bytes_le(),
Felt::ZERO.to_bytes_le(),
Felt::ZERO.to_bytes_le(),
Felt::ZERO.to_bytes_le(),
];

unsafe { cairo_native__libfunc__ec__ec_state_init(&mut state) };

let points: &mut [[u8; 32]; 2] = (&mut state[..2]).try_into().unwrap();

let result = unsafe { cairo_native__libfunc__ec__ec_point_try_new_nz(points) };

// point should be valid since it was made with state init
assert!(result);
}

#[test]
fn test_ec__ec_point_add() {
// Test values taken from starknet-rs
let mut state = [
Felt::from_dec_str(
"874739451078007766457464989774322083649278607533249481151382481072868806602",
)
.unwrap()
.to_bytes_le(),
Felt::from_dec_str(
"152666792071518830868575557812948353041420400780739481342941381225525861407",
)
.unwrap()
.to_bytes_le(),
Felt::from_dec_str(
"874739451078007766457464989774322083649278607533249481151382481072868806602",
)
.unwrap()
.to_bytes_le(),
Felt::from_dec_str(
"152666792071518830868575557812948353041420400780739481342941381225525861407",
)
.unwrap()
.to_bytes_le(),
];

let point = [
Felt::from_dec_str(
"874739451078007766457464989774322083649278607533249481151382481072868806602",
)
.unwrap()
.to_bytes_le(),
Felt::from_dec_str(
"152666792071518830868575557812948353041420400780739481342941381225525861407",
)
.unwrap()
.to_bytes_le(),
];

unsafe {
cairo_native__libfunc__ec__ec_state_add(&mut state, &point);
};

assert_eq!(
state[0],
Felt::from_dec_str(
"3324833730090626974525872402899302150520188025637965566623476530814354734325",
)
.unwrap()
.to_bytes_le()
);
assert_eq!(
state[1],
Felt::from_dec_str(
"3147007486456030910661996439995670279305852583596209647900952752170983517249",
)
.unwrap()
.to_bytes_le()
);
}
}
2 changes: 1 addition & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn felt(value: &str) -> [u32; 8] {
u32_digits.try_into().unwrap()
}

/// Parse any time that can be a bigint to a felt that can be used in the cairo-native input.
/// Parse any type that can be a bigint to a felt that can be used in the cairo-native input.
pub fn feltn(value: impl Into<BigInt>) -> [u32; 8] {
let value: BigInt = value.into();
let value = match value.sign() {
Expand Down
2 changes: 0 additions & 2 deletions tests/tests/boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,6 @@ lazy_static! {
};
}

// Since comparing a felt to 1 to create boolean (uses felt252_is_zero and felt sub,add) has a bug,
// we'll be using use u8 on other tests until this is fixed. The bug may be in felt subtraction.
#[test]
fn felt252_to_bool_bug() {
let program = &FELT252_TO_BOOL;
Expand Down

0 comments on commit 530d1e9

Please sign in to comment.