From 530d1e95ecf9c9ad1ec2d1694fc1b82e082a3d45 Mon Sep 17 00:00:00 2001 From: Edgar Date: Fri, 18 Oct 2024 16:13:31 +0200 Subject: [PATCH] Add runtime tests (#857) * runtime tests * dict tests * improve dict test * refund * tests * fix runtime desc * remove old comment * manually drop target fd * avoid possible overflow * fix typo --- runtime/Cargo.toml | 7 +- runtime/src/lib.rs | 242 ++++++++++++++++++++++++++++++++++++++++- tests/common.rs | 2 +- tests/tests/boolean.rs | 2 - 4 files changed, 242 insertions(+), 11 deletions(-) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index dc1ebd4c0..cdf46ad32 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -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" diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index fabe8de68..5e2c75ae7 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -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! { @@ -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); @@ -57,9 +60,6 @@ pub unsafe extern "C" fn cairo_native__libfunc__debug__print( return 1; }; - // Avoid closing `stdout`. - std::mem::forget(target); - 0 } @@ -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. @@ -648,3 +648,233 @@ pub fn as_cairo_short_string_ex(value: &Felt, length: usize) -> Option { 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 = 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() + ); + } +} diff --git a/tests/common.rs b/tests/common.rs index 9ca95d52b..b35a85e18 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -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) -> [u32; 8] { let value: BigInt = value.into(); let value = match value.sign() { diff --git a/tests/tests/boolean.rs b/tests/tests/boolean.rs index 039018485..7e14f919f 100644 --- a/tests/tests/boolean.rs +++ b/tests/tests/boolean.rs @@ -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;