diff --git a/Cargo.toml b/Cargo.toml index 2b4d952..d1f290c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +cairo-lang-compiler = "2.8.2" +cairo-lang-filesystem = "2.8.2" cairo-lang-sierra = "2.8.0" cairo-lang-sierra-ap-change = "2.8.0" cairo-lang-sierra-gas = "2.8.0" @@ -23,6 +25,7 @@ smallvec = "1.13.2" starknet-crypto = "0.7.1" starknet-curve = "0.5.0" starknet-types-core = "0.1.2" +tempfile = "3.13.0" thiserror = "1.0.63" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/src/lib.rs b/src/lib.rs index beb9a56..de8ec91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod debug; mod dump; mod gas; pub mod starknet; +mod test_utils; mod value; mod vm; diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000..672ccec --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,105 @@ +use std::{fs, path::Path, sync::Arc}; + +use cairo_lang_compiler::{ + compile_prepared_db, db::RootDatabase, diagnostics::DiagnosticsReporter, + project::setup_project, CompilerConfig, +}; +use cairo_lang_filesystem::db::init_dev_corelib; +use cairo_lang_sierra::{ + extensions::{ + circuit::CircuitTypeConcrete, core::CoreTypeConcrete, starknet::StarkNetTypeConcrete, + }, + program::Program, +}; + +use crate::{find_entry_point_by_idx, ProgramTrace, StateDump, Value, VirtualMachine}; + +#[macro_export] +macro_rules! load_cairo { + ( $( $program:tt )+ ) => { + $crate::test_utils::load_cairo_from_str(stringify!($($program)+)) + }; +} + +pub(crate) fn load_cairo_from_str(cairo_str: &str) -> (String, Program) { + let mut file = tempfile::Builder::new() + .prefix("test_") + .suffix(".cairo") + .tempfile() + .unwrap(); + let mut db = RootDatabase::default(); + + fs::write(&mut file, cairo_str).unwrap(); + + init_dev_corelib( + &mut db, + Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("corelib/src"), + ); + + let main_crate_ids = setup_project(&mut db, file.path()).unwrap(); + + let sierra_with_dbg = compile_prepared_db( + &db, + main_crate_ids, + CompilerConfig { + diagnostics_reporter: DiagnosticsReporter::stderr(), + replace_ids: true, + ..Default::default() + }, + ) + .unwrap(); + + let module_name = file.path().with_extension(""); + let module_name = module_name.file_name().unwrap().to_str().unwrap(); + (module_name.to_string(), sierra_with_dbg.program) +} + +pub fn run_test_program(sierra_program: Program) -> Vec { + let function = find_entry_point_by_idx(&sierra_program, 0).unwrap(); + + let mut vm = VirtualMachine::new(Arc::new(sierra_program.clone())); + + let initial_gas = 1000000; + + vm.push_frame( + function.id.clone(), + function + .signature + .param_types + .iter() + .map(|type_id| { + let type_info = vm.registry().get_type(type_id).unwrap(); + match type_info { + CoreTypeConcrete::GasBuiltin(_) => crate::Value::U128(initial_gas), + CoreTypeConcrete::StarkNet(StarkNetTypeConcrete::System(_)) => Value::Unit, + CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::RangeCheck96(_) + | CoreTypeConcrete::Pedersen(_) + | CoreTypeConcrete::Poseidon(_) + | CoreTypeConcrete::Bitwise(_) + | CoreTypeConcrete::BuiltinCosts(_) + | CoreTypeConcrete::SegmentArena(_) + | CoreTypeConcrete::Circuit( + CircuitTypeConcrete::AddMod(_) | CircuitTypeConcrete::MulMod(_), + ) => Value::Unit, + _ => unreachable!(), + } + }) + .collect::>(), + ); + + let mut trace = ProgramTrace::new(); + + while let Some((statement_idx, state)) = vm.step() { + trace.push(StateDump::new(statement_idx, state)); + } + + trace + .states + .last() + .unwrap() + .items + .values() + .cloned() + .collect() +} diff --git a/src/value.rs b/src/value.rs index 79ad98a..b0fbb32 100644 --- a/src/value.rs +++ b/src/value.rs @@ -52,6 +52,8 @@ pub enum Value { x1: Felt, y1: Felt, }, + I128(i128), + I32(i32), I8(i8), Struct(Vec), U256(u128, u128), @@ -105,6 +107,8 @@ impl Value { } CoreTypeConcrete::GasBuiltin(_) => matches!(self, Self::U128(_)), CoreTypeConcrete::NonZero(info) => self.is(registry, &info.ty), + CoreTypeConcrete::Sint128(_) => matches!(self, Self::I128(_)), + CoreTypeConcrete::Sint32(_) => matches!(self, Self::I32(_)), CoreTypeConcrete::Sint8(_) => matches!(self, Self::I8(_)), CoreTypeConcrete::Snapshot(info) => self.is(registry, &info.ty), CoreTypeConcrete::StarkNet( @@ -154,9 +158,7 @@ impl Value { CoreTypeConcrete::Uint64(_) => matches!(self, Self::U64(_)), CoreTypeConcrete::Uint128MulGuarantee(_) => matches!(self, Self::Unit), CoreTypeConcrete::Sint16(_) => todo!(), - CoreTypeConcrete::Sint32(_) => todo!(), CoreTypeConcrete::Sint64(_) => todo!(), - CoreTypeConcrete::Sint128(_) => todo!(), CoreTypeConcrete::Nullable(info) => self.is(registry, &info.ty), CoreTypeConcrete::Uninitialized(_) => todo!(), CoreTypeConcrete::Felt252DictEntry(_) => todo!(), diff --git a/src/vm.rs b/src/vm.rs index ac1327d..507b124 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -40,6 +40,7 @@ mod felt252_dict; mod felt252_dict_entry; mod function_call; mod gas; +mod int128; mod jump; mod mem; mod pedersen; @@ -331,7 +332,7 @@ fn eval<'a>( CoreConcreteLibfunc::Nullable(_) => todo!(), CoreConcreteLibfunc::Pedersen(selector) => self::pedersen::eval(registry, selector, args), CoreConcreteLibfunc::Poseidon(selector) => self::poseidon::eval(registry, selector, args), - CoreConcreteLibfunc::Sint128(_) => todo!(), + CoreConcreteLibfunc::Sint128(selector) => self::int128::eval(registry, selector, args), CoreConcreteLibfunc::Sint16(_) => todo!(), CoreConcreteLibfunc::Sint32(_) => todo!(), CoreConcreteLibfunc::Sint64(_) => todo!(), diff --git a/src/vm/cast.rs b/src/vm/cast.rs index c442c9e..30a1d03 100644 --- a/src/vm/cast.rs +++ b/src/vm/cast.rs @@ -50,6 +50,7 @@ pub fn eval_downcast( range_check, match registry.get_type(&info.to_ty).unwrap() { CoreTypeConcrete::Sint8(_) => Value::I8(value.try_into().unwrap()), + CoreTypeConcrete::Sint128(_) => Value::I128(value.try_into().unwrap()), CoreTypeConcrete::Uint8(_) => Value::U8(value.try_into().unwrap()), CoreTypeConcrete::Uint16(_) => Value::U16(value.try_into().unwrap()), CoreTypeConcrete::Uint32(_) => Value::U32(value.try_into().unwrap()), @@ -89,6 +90,8 @@ pub fn eval_upcast( .unwrap() { CoreTypeConcrete::Sint8(_) => Value::I8(value.try_into().unwrap()), + CoreTypeConcrete::Sint32(_) => Value::I32(value.try_into().unwrap()), + CoreTypeConcrete::Sint128(_) => Value::I128(value.try_into().unwrap()), CoreTypeConcrete::Uint8(_) => Value::U8(value.try_into().unwrap()), CoreTypeConcrete::Uint16(_) => Value::U16(value.try_into().unwrap()), CoreTypeConcrete::Uint32(_) => Value::U32(value.try_into().unwrap()), diff --git a/src/vm/const.rs b/src/vm/const.rs index 77e3f20..66ad442 100644 --- a/src/vm/const.rs +++ b/src/vm/const.rs @@ -56,10 +56,22 @@ pub fn eval_as_immediate( _ => unreachable!(), }, CoreTypeConcrete::NonZero(info) => inner(registry, &info.ty, inner_data), + CoreTypeConcrete::Sint128(_) => match inner_data { + [GenericArg::Value(value)] => Value::I128(value.try_into().unwrap()), + _ => unreachable!(), + }, + CoreTypeConcrete::Sint32(_) => match inner_data { + [GenericArg::Value(value)] => Value::I32(value.try_into().unwrap()), + _ => unreachable!(), + }, CoreTypeConcrete::Sint8(_) => match inner_data { [GenericArg::Value(value)] => Value::I8(value.try_into().unwrap()), _ => unreachable!(), }, + CoreTypeConcrete::Uint64(_) => match inner_data { + [GenericArg::Value(value)] => Value::U64(value.try_into().unwrap()), + _ => unreachable!(), + }, CoreTypeConcrete::Uint32(_) => match inner_data { [GenericArg::Value(value)] => Value::U32(value.try_into().unwrap()), _ => unreachable!(), @@ -95,7 +107,7 @@ pub fn eval_as_immediate( Value::Struct(fields) } - _ => todo!("{:?}", type_id), + _ => todo!("{}", type_id), } } diff --git a/src/vm/int128.rs b/src/vm/int128.rs new file mode 100644 index 0000000..e1cfd1a --- /dev/null +++ b/src/vm/int128.rs @@ -0,0 +1,196 @@ +use std::u128; + +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + int::{signed128::Sint128Concrete, IntOperationConcreteLibfunc, IntOperator}, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::{BigInt, BigUint, ToBigInt}; +use smallvec::smallvec; +use starknet_crypto::Felt; + +use crate::Value; + +use super::EvalAction; + +pub fn eval( + registry: &ProgramRegistry, + selector: &Sint128Concrete, + args: Vec, +) -> EvalAction { + match selector { + Sint128Concrete::Const(info) => todo!("1"), + Sint128Concrete::Operation(info) => eval_operation(registry, info, args), + Sint128Concrete::Equal(info) => eval_equal(registry, info, args), + Sint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), + Sint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Sint128Concrete::IsZero(info) => todo!("6"), + Sint128Concrete::Diff(info) => eval_diff(registry, info, args), + } +} + +fn eval_diff( + _registry: &ProgramRegistry, + _selector: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::I128(lhs), Value::I128(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + if lhs >= rhs { + EvalAction::NormalBranch( + 0, + smallvec![range_check, Value::U128((lhs - rhs).try_into().unwrap())], + ) + } else { + EvalAction::NormalBranch( + 1, + smallvec![ + range_check, + Value::U128(lhs.wrapping_sub(rhs).try_into().unwrap()) + ], + ) + } +} + +fn eval_operation( + _registry: &ProgramRegistry, + selector: &IntOperationConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::I128(lhs), Value::I128(rhs)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + let (result, overflow) = match selector.operator { + IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), + IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), + }; + + EvalAction::NormalBranch( + overflow as usize, + smallvec![range_check, Value::I128(result)], + ) +} + +fn eval_equal( + _registry: &ProgramRegistry, + _selector: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::I128(lhs), Value::I128(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +pub fn eval_to_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::I128(value)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) +} + +pub fn eval_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value_felt)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + let prime = Felt::prime(); + let half_prime = &prime / BigUint::from(2u8); + + let min = Felt::from(i128::MIN).to_bigint(); + let max = Felt::from(i128::MAX).to_bigint(); + + let value = { + if value_felt.to_biguint() > half_prime { + (prime - value_felt.to_biguint()).to_bigint().unwrap() * BigInt::from(-1) + } else { + value_felt.to_bigint() + } + }; + + if value >= min || value <= max { + let value: i128 = value.try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::I128(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +#[cfg(test)] +mod tests { + use crate::{load_cairo, test_utils::run_test_program, Value}; + + #[test] + fn test_eval_diff() { + let (_, program) = load_cairo!( + use core::integer; + + fn main() -> u128 { + integer::i128_diff(2, 1).unwrap() + } + ); + + let result = run_test_program(program); + + let Value::Enum { + self_ty: _, + index: _, + payload, + } = result.last().unwrap() + else { + panic!("No output"); + }; + + let expected = Value::Struct(vec![Value::U128(1)]); + + assert_eq!(**payload, expected); + } + + #[test] + fn test_eval_from_felt() { + let (_, program) = load_cairo!( + use core::integer; + + fn main() -> i128 { + 0x7fffffffffffffffffffffffffffffff_felt252 + .try_into() + .unwrap() + } + ); + + let result = run_test_program(program); + + let Value::Enum { + self_ty: _, + index: _, + payload, + } = result.last().unwrap() + else { + panic!("No output"); + }; + + let expected = Value::Struct(vec![Value::I128(0x7fffffffffffffffffffffffffffffff)]); + + assert_eq!(**payload, expected); + } +} diff --git a/src/vm/starknet.rs b/src/vm/starknet.rs index 75fee73..fa1401b 100644 --- a/src/vm/starknet.rs +++ b/src/vm/starknet.rs @@ -367,6 +367,7 @@ fn eval_storage_write( let [Value::U128(mut gas), system, Value::U32(address_domain), Value::Felt(storage_key), Value::Felt(value)]: [Value; 5] = args.try_into().unwrap() else { panic!() }; + let error_felt_ty = { match registry .get_type(&info.branch_signatures()[1].vars[2].ty)