diff --git a/CHANGELOG.md b/CHANGELOG.md index 29faf007d2..53928153d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: add `arbitrary` feature to enable arbitrary derive in `Program` and `CairoRunConfig` + * perf: remove pointless iterator from rc limits tracking [#1316](https://github.com/lambdaclass/cairo-vm/pull/1316) * feat: add `from_bytes_le` and `from_bytes_ne` methods [#1326](https://github.com/lambdaclass/cairo-vm/pull/1326) diff --git a/Cargo.lock b/Cargo.lock index b0014e20b6..db37539056 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,15 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -300,6 +309,7 @@ dependencies = [ name = "cairo-felt" version = "0.8.2" dependencies = [ + "arbitrary", "lambdaworks-math", "lazy_static", "num-bigint", @@ -714,6 +724,7 @@ name = "cairo-vm" version = "0.8.2" dependencies = [ "anyhow", + "arbitrary", "ark-ff", "ark-std", "assert_matches", @@ -1021,6 +1032,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] + [[package]] name = "diff" version = "0.1.13" @@ -1684,6 +1706,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ + "arbitrary", "autocfg", "num-integer", "num-traits 0.2.15", diff --git a/Cargo.toml b/Cargo.toml index a66169d427..efb03e7e38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,5 +61,8 @@ cairo-lang-casm = { version = "2.0.0", default-features = false } ark-ff = { version = "0.4.2", default-features = false } ark-std = { version = "0.4.0", default-features = false } +# For fuzzing +arbitrary = { version = "1.3.0", features = ["derive"] } + [profile.release] lto = "fat" diff --git a/felt/Cargo.toml b/felt/Cargo.toml index 36ea5e42db..fd41f03d44 100644 --- a/felt/Cargo.toml +++ b/felt/Cargo.toml @@ -12,6 +12,7 @@ default = ["std"] std = [] alloc = [] lambdaworks-felt = ["dep:lambdaworks-math"] +arbitrary = ["dep:arbitrary", "num-bigint/arbitrary"] [dependencies] num-integer = { version = "0.1.45", default-features = false } @@ -22,6 +23,7 @@ lazy_static = { version = "1.4.0", default-features = false, features = [ ] } serde = { version = "1.0", features = ["derive"], default-features = false } lambdaworks-math = { version = "0.1.2", default-features = false, optional = true } +arbitrary = { version = "1.3.0", features = ["derive"], optional = true } [dev-dependencies] proptest = "1.2.0" diff --git a/felt/src/bigint_felt.rs b/felt/src/bigint_felt.rs index 52a1ad747f..c1c6505a78 100644 --- a/felt/src/bigint_felt.rs +++ b/felt/src/bigint_felt.rs @@ -14,6 +14,9 @@ use core::{ use crate::{lib_bigint_felt::FeltOps, ParseFeltError}; +#[cfg(all(feature = "std", feature = "arbitrary"))] +use arbitrary::Arbitrary; + pub const FIELD_HIGH: u128 = (1 << 123) + (17 << 64); // this is equal to 10633823966279327296825105735305134080 pub const FIELD_LOW: u128 = 1; use lazy_static::lazy_static; @@ -31,6 +34,7 @@ lazy_static! { .expect("Conversion BigUint -> BigInt can't fail"); } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Deserialize, Default, Serialize)] pub(crate) struct FeltBigInt { val: BigUint, diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs index 186ff2a214..c539de2898 100644 --- a/felt/src/lib_bigint_felt.rs +++ b/felt/src/lib_bigint_felt.rs @@ -19,6 +19,9 @@ use core::{ #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::{string::String, vec::Vec}; +#[cfg(all(feature = "arbitrary", feature = "std"))] +use arbitrary::Arbitrary; + pub(crate) trait FeltOps { fn new>>(value: T) -> Self; @@ -66,6 +69,7 @@ macro_rules! felt_str { }; } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Deserialize, Default, Serialize)] pub struct Felt252 { pub(crate) value: FeltBigInt, diff --git a/felt/src/lib_lambdaworks.rs b/felt/src/lib_lambdaworks.rs index d42b4bb9f3..8c1f6335c5 100644 --- a/felt/src/lib_lambdaworks.rs +++ b/felt/src/lib_lambdaworks.rs @@ -22,8 +22,19 @@ use serde::{Deserialize, Serialize}; #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::{string::String, vec::Vec}; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; + use crate::{ParseFeltError, FIELD_HIGH, FIELD_LOW}; +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Felt252 { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let num: BigUint = BigUint::arbitrary(u)?; + Ok(Felt252::from(num)) + } +} + lazy_static! { pub static ref CAIRO_PRIME_BIGUINT: BigUint = (Into::::into(FIELD_HIGH) << 128) + Into::::into(FIELD_LOW); diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml index 18d1234654..f1f1c0bfc4 100644 --- a/fuzzer/Cargo.toml +++ b/fuzzer/Cargo.toml @@ -5,11 +5,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["."] + [dependencies] arbitrary = { version = "1.3.0", features = ["derive"] } honggfuzz = "0.5.55" bincode = { version = "2.0.0-rc.3", tag = "v2.0.0-rc.3", git = "https://github.com/bincode-org/bincode.git" } -cairo-vm = { path = "../vm" } +cairo-vm = { path = "../vm", features = ["arbitrary"] } mimalloc = { version = "0.1.29", default-features = false, optional = true } nom = "7" thiserror = { version = "1.0.32" } @@ -18,9 +21,6 @@ thiserror = { version = "1.0.32" } assert_matches = "1.5.0" rstest = "0.17.0" -[workspace] -members = ["."] - [features] default = ["with_mimalloc"] with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] @@ -28,3 +28,7 @@ with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] [[bin]] name = "fuzz_json" path = "src/fuzz_json.rs" + +[[bin]] +name = "fuzz_program" +path = "src/fuzz_program.rs" diff --git a/fuzzer/src/fuzz_program.rs b/fuzzer/src/fuzz_program.rs new file mode 100644 index 0000000000..0358ded6ee --- /dev/null +++ b/fuzzer/src/fuzz_program.rs @@ -0,0 +1,27 @@ +use cairo_vm::{ + cairo_run::{cairo_run_parsed_program, CairoRunConfig}, + hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, + types::program::Program, +}; +use honggfuzz::fuzz; + +const STEPS_LIMIT: usize = 1000000; +fn main() { + loop { + fuzz!(|data: (CairoRunConfig, Program)| { + let (cairo_config, program) = data; + let _ = cairo_run_parsed_program( + program.clone(), + &CairoRunConfig::default(), + &mut BuiltinHintProcessor::new_empty(), + STEPS_LIMIT, + ); + let _ = cairo_run_parsed_program( + program, + &cairo_config, + &mut BuiltinHintProcessor::new_empty(), + STEPS_LIMIT, + ); + }); + } +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 1a667bdb6d..d3937a0d02 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -25,6 +25,7 @@ cairo-1-hints = [ "dep:ark-ff", "dep:ark-std", ] +arbitrary = ["dep:arbitrary", "felt/arbitrary", "felt/std", "std"] lambdaworks-felt = ["felt/lambdaworks-felt"] # Note that these features are not retro-compatible with the cairo Python VM. @@ -69,6 +70,9 @@ cairo-lang-casm = { workspace = true, optional = true } ark-ff = { workspace = true, optional = true } ark-std = { workspace = true, optional = true } +# Enable arbitrary when fuzzing +arbitrary = { workspace = true, features = ["derive"], optional = true } + [dev-dependencies] assert_matches = "1.5.0" rstest = { version = "0.17.0", default-features = false } diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index f1a6ca0bc2..c47a50138b 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -14,6 +14,10 @@ use felt::Felt252; use thiserror_no_std::Error; +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; + +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct CairoRunConfig<'a> { pub entrypoint: &'a str, pub trace_enabled: bool, @@ -75,6 +79,43 @@ pub fn cairo_run( Ok((cairo_runner, vm)) } +#[cfg(feature = "arbitrary")] +pub fn cairo_run_parsed_program( + program: Program, + cairo_run_config: &CairoRunConfig, + hint_executor: &mut dyn HintProcessor, + steps_limit: usize, +) -> Result<(CairoRunner, VirtualMachine), CairoRunError> { + let secure_run = cairo_run_config + .secure_run + .unwrap_or(!cairo_run_config.proof_mode); + + let mut cairo_runner = CairoRunner::new( + &program, + cairo_run_config.layout, + cairo_run_config.proof_mode, + )?; + + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + + cairo_runner + .run_until_steps(steps_limit, &mut vm, hint_executor) + .map_err(|err| VmException::from_vm_error(&cairo_runner, &vm, err))?; + cairo_runner.end_run(false, false, &mut vm, hint_executor)?; + + vm.verify_auto_deductions()?; + cairo_runner.read_return_values(&mut vm)?; + if cairo_run_config.proof_mode { + cairo_runner.finalize_segments(&mut vm)?; + } + if secure_run { + verify_secure_runner(&cairo_runner, true, None, &mut vm)?; + } + cairo_runner.relocate(&mut vm, cairo_run_config.relocate_mem)?; + + Ok((cairo_runner, vm)) +} + #[derive(Debug, Error)] #[error("Failed to encode trace at position {0}, serialize error: {1}")] pub struct EncodeTraceError(usize, bincode::error::EncodeError); diff --git a/vm/src/hint_processor/hint_processor_definition.rs b/vm/src/hint_processor/hint_processor_definition.rs index 966a0f9f5f..6e2912af3d 100644 --- a/vm/src/hint_processor/hint_processor_definition.rs +++ b/vm/src/hint_processor/hint_processor_definition.rs @@ -14,6 +14,9 @@ use crate::vm::vm_core::VirtualMachine; use super::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData; use felt::Felt252; +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; + pub trait HintProcessorLogic { //Executes the hint which's data is provided by a dynamic structure previously created by compile_hint fn execute_hint( @@ -75,6 +78,7 @@ fn get_ids_data( Ok(ids_data) } +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Debug, PartialEq, Eq, Clone)] pub struct HintReference { pub offset1: OffsetValue, diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index 3c4155d9b5..60e59dab48 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -21,7 +21,11 @@ use num_traits::{Num, Pow}; use serde::{de, de::MapAccess, de::SeqAccess, Deserialize, Deserializer, Serialize}; use serde_json::Number; +#[cfg(all(feature = "arbitrary", feature = "std"))] +use arbitrary::Arbitrary; + // This enum is used to deserialize program builtins into &str and catch non-valid names +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Eq, Hash)] #[allow(non_camel_case_types)] pub enum BuiltinName { @@ -65,6 +69,7 @@ pub struct ProgramJson { pub debug_info: Option, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct HintParams { pub code: String, @@ -72,6 +77,7 @@ pub struct HintParams { pub flow_tracking_data: FlowTrackingData, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct FlowTrackingData { pub ap_tracking: ApTracking, @@ -79,6 +85,7 @@ pub struct FlowTrackingData { pub reference_ids: HashMap, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct ApTracking { pub group: usize, @@ -100,6 +107,7 @@ impl Default for ApTracking { } } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Identifier { pub pc: Option, @@ -114,12 +122,14 @@ pub struct Identifier { pub cairo_type: Option, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Member { pub cairo_type: String, pub offset: usize, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Attribute { pub name: String, @@ -129,6 +139,7 @@ pub struct Attribute { pub flow_tracking_data: Option, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct Location { pub end_line: u32, @@ -144,17 +155,20 @@ pub struct DebugInfo { instruction_locations: HashMap, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct InstructionLocation { pub inst: Location, pub hints: Vec, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct InputFile { pub filename: String, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct HintLocation { pub location: Location, @@ -211,6 +225,7 @@ pub struct Reference { pub value_address: ValueAddress, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum OffsetValue { Immediate(Felt252), diff --git a/vm/src/types/instruction.rs b/vm/src/types/instruction.rs index fff2933c26..8d1e3f2f9c 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -4,6 +4,10 @@ use serde::{Deserialize, Serialize}; use crate::vm::decoding::decoder::decode_instruction; +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; + +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)] pub enum Register { AP, diff --git a/vm/src/types/program.rs b/vm/src/types/program.rs index 2b0b2b709d..ec9090fdbc 100644 --- a/vm/src/types/program.rs +++ b/vm/src/types/program.rs @@ -19,6 +19,9 @@ use felt::{Felt252, PRIME_STR}; #[cfg(feature = "std")] use std::path::Path; +#[cfg(all(feature = "arbitrary", feature = "std"))] +use arbitrary::Arbitrary; + // NOTE: `Program` has been split in two containing some data that will be deep-copied // and some that will be allocated on the heap inside an `Arc<_>`. // This is because it has been reported that cloning the whole structure when creating @@ -40,6 +43,7 @@ use std::path::Path; // exceptional circumstances, such as when reconstructing a backtrace on execution // failures. // Fields in `Program` (other than `SharedProgramData` itself) are used by the main logic. +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Clone, Default, Debug, PartialEq, Eq)] pub(crate) struct SharedProgramData { pub(crate) data: Vec, @@ -54,6 +58,7 @@ pub(crate) struct SharedProgramData { pub(crate) reference_manager: Vec, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Program { pub(crate) shared_program_data: Arc, diff --git a/vm/src/types/relocatable.rs b/vm/src/types/relocatable.rs index 324a48445b..14f3c1bbdf 100644 --- a/vm/src/types/relocatable.rs +++ b/vm/src/types/relocatable.rs @@ -11,12 +11,17 @@ use felt::Felt252; use num_traits::{ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; +#[cfg(all(feature = "arbitrary", feature = "std"))] +use arbitrary::Arbitrary; + +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)] pub struct Relocatable { pub segment_index: isize, pub offset: usize, } +#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)] pub enum MaybeRelocatable { RelocatableValue(Relocatable),