diff --git a/CHANGELOG.md b/CHANGELOG.md index fee05bc0f3..7806191aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ ## Cairo-VM Changelog #### Upcoming Changes + +* feat(BREAKING): Use return type info from sierra when serializing return values in cairo1-run crate [#1665](https://github.com/lambdaclass/cairo-vm/pull/1665) + * Removed public function `serialize_output`. + * Add field `serialize_output` to `Cairo1RunConfig`. + * Function `cairo_run_program` now returns an extra `Option` value with the serialized output if `serialize_output` is enabled in the config. + * Output serialization improved as it now uses the sierra program data to identify return value's types. + * feat: add a `--tracer` option which hosts a web server that shows the line by line execution of cairo code along with memory registers [#1265](https://github.com/lambdaclass/cairo-vm/pull/1265) * feat: Fix error handling in `initialize_state`[#1657](https://github.com/lambdaclass/cairo-vm/pull/1657) diff --git a/cairo1-run/Cargo.toml b/cairo1-run/Cargo.toml index 391774f86a..a831387a82 100644 --- a/cairo1-run/Cargo.toml +++ b/cairo1-run/Cargo.toml @@ -26,6 +26,7 @@ bincode.workspace = true assert_matches = "1.5.0" rstest = "0.17.0" mimalloc = { version = "0.1.37", default-features = false, optional = true } +num-traits = { version = "0.2", default-features = false } [features] default = ["with_mimalloc"] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 8376727918..fec7569fad 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -13,11 +13,11 @@ use cairo_lang_sierra::{ ConcreteType, NamedType, }, ids::ConcreteTypeId, - program::{Function, Program as SierraProgram}, + program::{Function, GenericArg, Program as SierraProgram}, program_registry::ProgramRegistry, }; use cairo_lang_sierra_ap_change::calc_ap_changes; -use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_gas::{gas_info::GasInfo, objects::CostInfoProvider}; use cairo_lang_sierra_to_casm::{ compiler::CairoProgram, metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, @@ -26,6 +26,7 @@ use cairo_lang_sierra_type_size::get_type_size_map; use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use cairo_vm::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + math_utils::signed_felt, serde::deserialize_program::{ ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, }, @@ -43,14 +44,17 @@ use cairo_vm::{ }, Felt252, }; -use itertools::chain; -use std::collections::HashMap; +use itertools::{chain, Itertools}; +use num_traits::{cast::ToPrimitive, Zero}; +use std::{collections::HashMap, iter::Peekable, slice::Iter}; use crate::{Error, FuncArg}; #[derive(Debug)] pub struct Cairo1RunConfig<'a> { pub args: &'a [FuncArg], + // Serializes program output into a user-friendly format + pub serialize_output: bool, pub trace_enabled: bool, pub relocate_mem: bool, pub layout: &'a str, @@ -66,6 +70,7 @@ impl Default for Cairo1RunConfig<'_> { fn default() -> Self { Self { args: Default::default(), + serialize_output: false, trace_enabled: false, relocate_mem: false, layout: "plain", @@ -77,11 +82,19 @@ impl Default for Cairo1RunConfig<'_> { } // Runs a Cairo 1 program -// Returns the runner & VM after execution + the return values +// Returns the runner & VM after execution + the return values + the serialized return values (if serialize_output is enabled) pub fn cairo_run_program( sierra_program: &SierraProgram, cairo_run_config: Cairo1RunConfig, -) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { +) -> Result< + ( + CairoRunner, + VirtualMachine, + Vec, + Option, + ), + Error, +> { let metadata = create_metadata(sierra_program, Some(Default::default()))?; let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; let type_sizes = @@ -211,6 +224,18 @@ pub fn cairo_run_program( // Fetch return values let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + let serialized_output = if cairo_run_config.serialize_output { + Some(serialize_output( + &return_values, + &mut vm, + return_type_id, + &sierra_program_registry, + &type_sizes, + )) + } else { + None + }; + // Set stop pointers for builtins so we can obtain the air public input if cairo_run_config.finalize_builtins { finalize_builtins( @@ -233,7 +258,7 @@ pub fn cairo_run_program( runner.relocate(&mut vm, true)?; - Ok((runner, vm, return_values)) + Ok((runner, vm, return_values, serialized_output)) } fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { @@ -726,6 +751,371 @@ fn finalize_builtins( Ok(()) } +fn serialize_output( + return_values: &[MaybeRelocatable], + vm: &mut VirtualMachine, + return_type_id: &ConcreteTypeId, + sierra_program_registry: &ProgramRegistry, + type_sizes: &UnorderedHashMap, +) -> String { + let mut output_string = String::new(); + let mut return_values_iter: Peekable> = return_values.iter().peekable(); + serialize_output_inner( + &mut return_values_iter, + &mut output_string, + vm, + return_type_id, + sierra_program_registry, + type_sizes, + ); + output_string +} + +fn serialize_output_inner( + return_values_iter: &mut Peekable>, + output_string: &mut String, + vm: &mut VirtualMachine, + return_type_id: &ConcreteTypeId, + sierra_program_registry: &ProgramRegistry, + type_sizes: &UnorderedHashMap, +) { + match sierra_program_registry.get_type(return_type_id).unwrap() { + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Array(info) => { + // Fetch array from memory + let array_start = return_values_iter + .next() + .expect("Missing return value") + .get_relocatable() + .expect("Array start_ptr not Relocatable"); + // Arrays can come in two formats: either [start_ptr, end_ptr] or [end_ptr], with the start_ptr being implicit (base of the end_ptr's segment) + let (array_start, array_size ) = match return_values_iter.peek().and_then(|mr| mr.get_relocatable()) { + Some(array_end) if array_end.segment_index == array_start.segment_index && array_end.offset >= array_start.offset => { + // Pop the value we just peeked + return_values_iter.next(); + (array_start, (array_end - array_start).unwrap()) + } + _ => ((array_start.segment_index, 0).into(), array_start.offset), + }; + let array_data = vm.get_continuous_range(array_start, array_size).unwrap(); + let mut array_data_iter = array_data.iter().peekable(); + let array_elem_id = &info.ty; + // Serialize array data + maybe_add_whitespace(output_string); + output_string.push('['); + while array_data_iter.peek().is_some() { + serialize_output_inner( + &mut array_data_iter, + output_string, + vm, + array_elem_id, + sierra_program_registry, + type_sizes, + ) + } + output_string.push(']'); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Box(info) => { + // As this represents a pointer, we need to extract it's values + let ptr = return_values_iter + .next() + .expect("Missing return value") + .get_relocatable() + .expect("Box Pointer is not Relocatable"); + let type_size = type_sizes.type_size(&info.ty); + let data = vm + .get_continuous_range(ptr, type_size) + .expect("Failed to extract value from nullable ptr"); + let mut data_iter = data.iter().peekable(); + serialize_output_inner( + &mut data_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Const(_) => { + unimplemented!("Not supported in the current version") + }, + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Felt252(_) + // Only unsigned integer values implement Into + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Bytes31(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint8(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint16(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint32(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint64(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Uint128(_) => { + maybe_add_whitespace(output_string); + let val = return_values_iter + .next() + .expect("Missing return value") + .get_int() + .expect("Value is not an integer"); + output_string.push_str(&val.to_string()); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint8(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint16(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint32(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint64(_) + | cairo_lang_sierra::extensions::core::CoreTypeConcrete::Sint128(_) => { + maybe_add_whitespace(output_string); + let val = return_values_iter + .next() + .expect("Missing return value") + .get_int() + .expect("Value is not an integer"); + output_string.push_str(&signed_felt(val).to_string()); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::NonZero(info) => { + serialize_output_inner( + return_values_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Nullable(info) => { + // As this represents a pointer, we need to extract it's values + let ptr = match return_values_iter.next().expect("Missing return value") { + MaybeRelocatable::RelocatableValue(ptr) => *ptr, + MaybeRelocatable::Int(felt) if felt.is_zero() => { + // Nullable is Null + maybe_add_whitespace(output_string); + output_string.push_str("null"); + return; + } + _ => panic!("Invalid Nullable"), + }; + let type_size = type_sizes.type_size(&info.ty); + let data = vm + .get_continuous_range(ptr, type_size) + .expect("Failed to extract value from nullable ptr"); + let mut data_iter = data.iter().peekable(); + serialize_output_inner( + &mut data_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Enum(info) => { + // First we check if it is a Panic enum, as we already handled panics when fetching return values, + // we can ignore them and move on to the non-panic variant + if let GenericArg::UserType(user_type) = &info.info.long_id.generic_args[0] { + if user_type + .debug_name + .as_ref() + .is_some_and(|n| n.starts_with("core::panics::PanicResult")) + { + return serialize_output_inner( + return_values_iter, + output_string, + vm, + &info.variants[0], + sierra_program_registry, + type_sizes, + ); + } + } + let num_variants = &info.variants.len(); + let casm_variant_idx: usize = return_values_iter + .next() + .expect("Missing return value") + .get_int() + .expect("Enum tag is not integer") + .to_usize() + .expect("Invalid enum tag"); + // Convert casm variant idx to sierra variant idx + let variant_idx = if *num_variants > 2 { + num_variants - 1 - (casm_variant_idx >> 1) + } else { + casm_variant_idx + }; + let variant_type_id = &info.variants[variant_idx]; + + // Handle core::bool separately + if let GenericArg::UserType(user_type) = &info.info.long_id.generic_args[0] { + if user_type + .debug_name + .as_ref() + .is_some_and(|n| n == "core::bool") + { + // Sanity checks + assert!( + *num_variants == 2 + && variant_idx < 2 + && type_sizes + .get(&info.variants[0]) + .is_some_and(|size| size.is_zero()) + && type_sizes + .get(&info.variants[1]) + .is_some_and(|size| size.is_zero()), + "Malformed bool enum" + ); + + let boolean_string = match variant_idx { + 0 => "false", + _ => "true", + }; + maybe_add_whitespace(output_string); + output_string.push_str(boolean_string); + return; + } + } + // TODO: Something similar to the bool handling could be done for unit enum variants if we could get the type info with the variant names + + // Space is always allocated for the largest enum member, padding with zeros in front for the smaller variants + let mut max_variant_size = 0; + for variant in &info.variants { + let variant_size = type_sizes.get(variant).unwrap(); + max_variant_size = std::cmp::max(max_variant_size, *variant_size) + } + for _ in 0..max_variant_size - type_sizes.get(variant_type_id).unwrap() { + // Remove padding + assert_eq!( + return_values_iter.next(), + Some(&MaybeRelocatable::from(0)), + "Malformed enum" + ); + } + serialize_output_inner( + return_values_iter, + output_string, + vm, + variant_type_id, + sierra_program_registry, + type_sizes, + ) + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Struct(info) => { + for member_type_id in &info.members { + serialize_output_inner( + return_values_iter, + output_string, + vm, + member_type_id, + sierra_program_registry, + type_sizes, + ) + } + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Felt252Dict(info) => { + // Process Dictionary + let dict_ptr = return_values_iter + .next() + .expect("Missing return val") + .get_relocatable() + .expect("Dict Ptr not Relocatable"); + if !(dict_ptr.offset + == vm + .get_segment_size(dict_ptr.segment_index as usize) + .unwrap_or_default() + && dict_ptr.offset % 3 == 0) + { + panic!("Return value is not a valid Felt252Dict") + } + // Fetch dictionary values type id + let value_type_id = &info.ty; + // Fetch the dictionary's memory + let dict_mem = vm + .get_continuous_range((dict_ptr.segment_index, 0).into(), dict_ptr.offset) + .expect("Malformed dictionary memory"); + // Serialize the dictionary + output_string.push('{'); + // The dictionary's memory is made up of (key, prev_value, next_value) tuples + // The prev value is not relevant to the user so we can skip over it for calrity + for (key, _, value) in dict_mem.iter().tuples() { + maybe_add_whitespace(output_string); + // Serialize the key wich should always be a Felt value + output_string.push_str(&key.to_string()); + output_string.push(':'); + // Serialize the value + // We create a peekable array here in order to use the serialize_output_inner as the value could be a span + let value_vec = vec![value.clone()]; + let mut value_iter: Peekable> = value_vec.iter().peekable(); + serialize_output_inner( + &mut value_iter, + output_string, + vm, + value_type_id, + sierra_program_registry, + type_sizes, + ); + } + output_string.push('}'); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::SquashedFelt252Dict(info) => { + // Process Dictionary + let dict_start = return_values_iter + .next() + .expect("Missing return val") + .get_relocatable() + .expect("Squashed dict_start ptr not Relocatable"); + let dict_end = return_values_iter + .next() + .expect("Missing return val") + .get_relocatable() + .expect("Squashed dict_end ptr not Relocatable"); + let dict_size = (dict_end - dict_start).unwrap(); + if dict_size % 3 != 0 { + panic!("Return value is not a valid SquashedFelt252Dict") + } + // Fetch dictionary values type id + let value_type_id = &info.ty; + // Fetch the dictionary's memory + let dict_mem = vm + .get_continuous_range(dict_start, dict_size) + .expect("Malformed squashed dictionary memory"); + // Serialize the dictionary + output_string.push('{'); + // The dictionary's memory is made up of (key, prev_value, next_value) tuples + // The prev value is not relevant to the user so we can skip over it for calrity + for (key, _, value) in dict_mem.iter().tuples() { + maybe_add_whitespace(output_string); + // Serialize the key wich should always be a Felt value + output_string.push_str(&key.to_string()); + output_string.push(':'); + // Serialize the value + // We create a peekable array here in order to use the serialize_output_inner as the value could be a span + let value_vec = vec![value.clone()]; + let mut value_iter: Peekable> = value_vec.iter().peekable(); + serialize_output_inner( + &mut value_iter, + output_string, + vm, + value_type_id, + sierra_program_registry, + type_sizes, + ); + } + output_string.push('}'); + } + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Span(_) => unimplemented!("Span types get resolved to Array in the current version"), + cairo_lang_sierra::extensions::core::CoreTypeConcrete::Snapshot(info) => { + serialize_output_inner( + return_values_iter, + output_string, + vm, + &info.ty, + sierra_program_registry, + type_sizes, + ) + } + _ => panic!("Unexpected return type") + } +} + +fn maybe_add_whitespace(string: &mut String) { + if !string.is_empty() && !string.ends_with('[') && !string.ends_with('{') { + string.push(' '); + } +} + #[cfg(test)] mod tests { use std::path::Path; @@ -791,7 +1181,7 @@ mod tests { ..Default::default() }; // Run program - let (runner, vm, return_values) = + let (runner, vm, return_values, _) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values // And handle the panics returning an error, so we need to add it here diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index e9875babb7..e8aeabadb6 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -6,13 +6,10 @@ use cairo_run::Cairo1RunConfig; use cairo_vm::{ air_public_input::PublicInputError, cairo_run::EncodeTraceError, - types::{errors::program_errors::ProgramError, relocatable::MaybeRelocatable}, - vm::{ - errors::{ - memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, - vm_errors::VirtualMachineError, - }, - vm_core::VirtualMachine, + types::errors::program_errors::ProgramError, + vm::errors::{ + memory_errors::MemoryError, runner_errors::RunnerError, trace_errors::TraceError, + vm_errors::VirtualMachineError, }, Felt252, }; @@ -20,9 +17,7 @@ use clap::{Parser, ValueHint}; use itertools::Itertools; use std::{ io::{self, Write}, - iter::Peekable, path::PathBuf, - slice::Iter, }; use thiserror::Error; @@ -216,6 +211,7 @@ fn run(args: impl Iterator) -> Result, Error> { let cairo_run_config = Cairo1RunConfig { proof_mode: args.proof_mode, + serialize_output: args.print_output, relocate_mem: args.memory_file.is_some() || args.air_public_input.is_some(), layout: &args.layout, trace_enabled: args.trace_file.is_some() || args.air_public_input.is_some(), @@ -232,15 +228,9 @@ fn run(args: impl Iterator) -> Result, Error> { let sierra_program = compile_cairo_project_at_path(&args.filename, compiler_config) .map_err(|err| Error::SierraCompilation(err.to_string()))?; - let (runner, vm, return_values) = + let (runner, vm, _, serialized_output) = cairo_run::cairo_run_program(&sierra_program, cairo_run_config)?; - let output_string = if args.print_output { - Some(serialize_output(&vm, &return_values)) - } else { - None - }; - if let Some(file_path) = args.air_public_input { let json = runner.get_air_public_input(&vm)?.serialize_json()?; std::fs::write(file_path, json)?; @@ -297,7 +287,7 @@ fn run(args: impl Iterator) -> Result, Error> { memory_writer.flush()?; } - Ok(output_string) + Ok(serialized_output) } fn main() -> Result<(), Error> { @@ -331,116 +321,6 @@ fn main() -> Result<(), Error> { } } -/// Serializes the return values in a user-friendly format -/// Displays Arrays using brackets ([]) and Dictionaries using ({}) -/// Recursively dereferences referenced values (such as Span & Box) -pub fn serialize_output(vm: &VirtualMachine, return_values: &[MaybeRelocatable]) -> String { - let mut output_string = String::new(); - let mut return_values_iter: Peekable> = return_values.iter().peekable(); - serialize_output_inner(&mut return_values_iter, &mut output_string, vm); - fn serialize_output_inner( - iter: &mut Peekable>, - output_string: &mut String, - vm: &VirtualMachine, - ) { - while let Some(val) = iter.next() { - if let MaybeRelocatable::RelocatableValue(x) = val { - // Check if the next value is a relocatable of the same index - if let Some(MaybeRelocatable::RelocatableValue(y)) = iter.peek() { - // Check if the two relocatable values represent a valid array in memory - if x.segment_index == y.segment_index && x.offset <= y.offset { - // Fetch the y value from the iterator so we don't serialize it twice - iter.next(); - // Fetch array - maybe_add_whitespace(output_string); - output_string.push('['); - let array = vm.get_continuous_range(*x, y.offset - x.offset).unwrap(); - let mut array_iter: Peekable> = - array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm); - output_string.push(']'); - continue; - } - } - - // Check if the single relocatable value represents a span - // In this case, the reloacatable will point us to the (start, end) pair in memory - // For example, the relocatable value may be 14:0, with the segment 14 containing [13:0 13:4] which is a valid array - if let (Ok(x), Ok(y)) = (vm.get_relocatable(*x), vm.get_relocatable(x + 1)) { - if x.segment_index == y.segment_index && y.offset >= x.offset { - // Fetch array - maybe_add_whitespace(output_string); - output_string.push('['); - let array = vm.get_continuous_range(x, y.offset - x.offset).unwrap(); - let mut array_iter: Peekable> = - array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm); - output_string.push(']'); - continue; - } - } - - // Check if the relocatable value represents a dictionary - // To do so we can check if the relocatable consists of the last dict_ptr, which should be a pointer to the next empty cell in the dictionary's segment - // We can check that the dict_ptr's offset is consistent with the length of the segment and that the segment is made up of tuples of three elements (key, prev_val, val) - if x.offset - == vm - .get_segment_size(x.segment_index as usize) - .unwrap_or_default() - && x.offset % 3 == 0 - { - // Fetch the dictionary's memory - let dict_mem = vm - .get_continuous_range((x.segment_index, 0).into(), x.offset) - .expect("Malformed dictionary memory"); - // Serialize the dictionary - output_string.push('{'); - // The dictionary's memory is made up of (key, prev_value, next_value) tuples - // The prev value is not relevant to the user so we can skip over it for calrity - for (key, _, value) in dict_mem.iter().tuples() { - maybe_add_whitespace(output_string); - // Serialize the key wich should always be a Felt value - output_string.push_str(&key.to_string()); - output_string.push(':'); - // Serialize the value - // We create a peekable array here in order to use the serialize_output_inner as the value could be a span - let value_vec = vec![value.clone()]; - let mut value_iter: Peekable> = - value_vec.iter().peekable(); - serialize_output_inner(&mut value_iter, output_string, vm); - } - output_string.push('}'); - continue; - } - - // Finally, if the relocatable is neither the start of an array, a span, or a dictionary, it should be a reference (Such as Box) - // In this case we show the referenced value (if it exists) - // As this reference can also hold a reference we use the serialize_output_inner function to handle it recursively - if let Some(val) = vm.get_maybe(x) { - maybe_add_whitespace(output_string); - let array = vec![val.clone()]; - let mut array_iter: Peekable> = array.iter().peekable(); - serialize_output_inner(&mut array_iter, output_string, vm); - continue; - } - } - maybe_add_whitespace(output_string); - output_string.push_str(&val.to_string()); - } - } - - fn maybe_add_whitespace(string: &mut String) { - if !string.is_empty() - && !string.ends_with('[') - && !string.ends_with(':') - && !string.ends_with('{') - { - string.push(' '); - } - } - output_string -} - #[cfg(test)] mod tests { use super::*; @@ -492,7 +372,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/hello.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_hello_ok(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(Some(res)) if res == "1 1234"); + assert_matches!(run(args), Ok(Some(res)) if res == "1234"); } #[rstest] @@ -556,7 +436,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/simple.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_simple_ok(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(Some(res)) if res == "1"); + assert_matches!(run(args), Ok(Some(res)) if res == "true"); } #[rstest] @@ -644,7 +524,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_felt_dict(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - let expected_output = "{66675:[8 9 10 11] 66676:[1 2 3]}"; + let expected_output = "{66675: [8 9 10 11] 66676: [1 2 3]}"; assert_matches!(run(args), Ok(Some(res)) if res == expected_output); } @@ -671,7 +551,52 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/nullable_box_vec.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] fn test_run_nullable_box_vec(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - let expected_output = "{0:10 1:20 2:30} 3"; + let expected_output = "{0: 10 1: 20 2: 30} 3"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/dict_with_struct.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/dict_with_struct.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_dict_with_struct(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "{0: 1 true 1: 1 false 2: 1 true}"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict_squash.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/felt_dict_squash.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_felt_dict_squash(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "{66675: [4 5 6] 66676: [1 2 3]}"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/null_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/null_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_null_ret(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "null"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/bytes31_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/bytes31_ret.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_bytes31_ret(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "123"; + assert_matches!(run(args), Ok(Some(res)) if res == expected_output); + } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/tensor_new.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/tensor_new.cairo", "--print_output", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo", "--proof_mode", "--air_public_input", "/dev/null", "--air_private_input", "/dev/null"].as_slice())] + fn test_run_tensor_new(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + let expected_output = "[1 2] [1 false 1 true]"; assert_matches!(run(args), Ok(Some(res)) if res == expected_output); } } diff --git a/cairo_programs/cairo-1-programs/bytes31_ret.cairo b/cairo_programs/cairo-1-programs/bytes31_ret.cairo new file mode 100644 index 0000000000..3a67f9cbf5 --- /dev/null +++ b/cairo_programs/cairo-1-programs/bytes31_ret.cairo @@ -0,0 +1,5 @@ +fn main() -> bytes31 { + let a: u128 = 123; + let b: bytes31 = a.into(); + b +} diff --git a/cairo_programs/cairo-1-programs/dict_with_struct.cairo b/cairo_programs/cairo-1-programs/dict_with_struct.cairo new file mode 100644 index 0000000000..e24df874c2 --- /dev/null +++ b/cairo_programs/cairo-1-programs/dict_with_struct.cairo @@ -0,0 +1,24 @@ +use core::nullable::{nullable_from_box, match_nullable, FromNullableResult}; + + +#[derive(Drop, Copy)] +struct FP16x16 { + mag: u32, + sign: bool +} + +fn main() -> Felt252Dict> { + // Create the dictionary + let mut d: Felt252Dict> = Default::default(); + + let box_a = BoxTrait::new(FP16x16 { mag: 1, sign: false }); + let box_b = BoxTrait::new(FP16x16 { mag: 1, sign: true }); + let box_c = BoxTrait::new(FP16x16 { mag: 1, sign: true }); + + // Insert it as a `Span` + d.insert(0, nullable_from_box(box_c)); + d.insert(1, nullable_from_box(box_a)); + d.insert(2, nullable_from_box(box_b)); + + d +} diff --git a/cairo_programs/cairo-1-programs/felt_dict_squash.cairo b/cairo_programs/cairo-1-programs/felt_dict_squash.cairo new file mode 100644 index 0000000000..7f1e284e32 --- /dev/null +++ b/cairo_programs/cairo-1-programs/felt_dict_squash.cairo @@ -0,0 +1,18 @@ +use core::nullable::{nullable_from_box, match_nullable, FromNullableResult}; +use core::dict::Felt252DictEntry; + +fn main() -> SquashedFelt252Dict>> { + // Create the dictionary + let mut d: Felt252Dict>> = Default::default(); + + // Create the array to insert + let a = array![8, 9, 10, 11]; + let b = array![1, 2, 3]; + let c = array![4, 5, 6]; + + // Insert it as a `Span` + d.insert(66675, nullable_from_box(BoxTrait::new(a.span()))); + d.insert(66676, nullable_from_box(BoxTrait::new(b.span()))); + d.insert(66675, nullable_from_box(BoxTrait::new(c.span()))); + d.squash() +} diff --git a/cairo_programs/cairo-1-programs/null_ret.cairo b/cairo_programs/cairo-1-programs/null_ret.cairo new file mode 100644 index 0000000000..85769a8e15 --- /dev/null +++ b/cairo_programs/cairo-1-programs/null_ret.cairo @@ -0,0 +1,3 @@ +fn main() -> Nullable { + null() +} diff --git a/cairo_programs/cairo-1-programs/tensor_new.cairo b/cairo_programs/cairo-1-programs/tensor_new.cairo new file mode 100644 index 0000000000..2af717beed --- /dev/null +++ b/cairo_programs/cairo-1-programs/tensor_new.cairo @@ -0,0 +1,65 @@ +// FP16x16 +#[derive(Serde, Copy, Drop)] +struct FP16x16 { + mag: u32, + sign: bool +} + +trait FixedTrait { + fn new(mag: MAG, sign: bool) -> T; +} + +impl FP16x16Impl of FixedTrait { + fn new(mag: u32, sign: bool) -> FP16x16 { + FP16x16 { mag: mag, sign: sign } + } +} + +//Tensor +#[derive(Copy, Drop)] +struct Tensor { + shape: Span, + data: Span, +} + +trait TensorTrait { + fn new(shape: Span, data: Span) -> Tensor; +} + +impl FP16x16Tensor of TensorTrait { + fn new(shape: Span, data: Span) -> Tensor { + new_tensor(shape, data) + } +} + +fn new_tensor(shape: Span, data: Span) -> Tensor { + check_shape::(shape, data); + Tensor:: { shape, data } +} + +fn check_shape(shape: Span, data: Span) { + assert(len_from_shape(shape) == data.len(), 'wrong tensor shape'); +} + +fn len_from_shape(mut shape: Span) -> usize { + let mut result: usize = 1; + + loop { + match shape.pop_front() { + Option::Some(item) => { result *= *item; }, + Option::None => { break; } + }; + }; + + result +} + +fn main() -> Tensor { + TensorTrait::new( + array![1, 2].span(), + array![ + FixedTrait::new(1, false), + FixedTrait::new(1, true) + ].span() + ) +}