From c8892fe1d6fbb912bb15d2fc522530007f1a2473 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Mon, 8 Jun 2026 17:30:52 +0300 Subject: [PATCH 01/21] macros: add `RandomArguments` and `RandomWitness` trait implementations for witness and arguments respectively * extend ui tests to test random generation * add new `RandomArguments` and `RandomWitness` traits for classifying random generation * add random generation expansion in the `include_simf!` macro * add `rand_core` and `rand` dependencies in the workspace --- Cargo.lock | 47 ++++++++++++--- Cargo.toml | 2 + crates/build/Cargo.toml | 5 +- crates/build/src/macros/codegen.rs | 49 ++++++++++++++++ crates/build/src/macros/types.rs | 57 ++++++++++++++++++- crates/sdk/Cargo.toml | 1 + crates/sdk/src/program/arguments.rs | 9 ++- crates/sdk/src/program/mod.rs | 4 +- crates/sdk/src/program/witness.rs | 9 ++- crates/simplex/Cargo.toml | 2 + crates/simplex/src/lib.rs | 2 + crates/simplex/tests/ui/array_tr_storage.rs | 35 +++++++++++- crates/simplex/tests/ui/bytes32_tr_storage.rs | 35 +++++++++++- .../simplex/tests/ui/dual_currency_deposit.rs | 35 +++++++++++- .../tests/ui/either_with_single_witness.rs | 35 +++++++++++- crates/simplex/tests/ui/exotic_values.rs | 29 +++++++++- crates/simplex/tests/ui/list_check.rs | 29 +++++++++- crates/simplex/tests/ui/option_offer.rs | 29 +++++++++- crates/simplex/tests/ui/options.rs | 29 +++++++++- crates/simplex/tests/ui/simple_storage.rs | 29 +++++++++- crates/simplex/tests/ui/single_bit.rs | 29 +++++++++- examples/basic/Cargo.lock | 47 ++++++++++++--- fixtures/Cargo.lock | 47 ++++++++++++--- 23 files changed, 557 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1123bfd7..1c039a3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,8 +150,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ "bitcoin_hashes", - "rand", - "rand_core", + "rand 0.8.6", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -1065,8 +1065,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1076,7 +1086,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1088,6 +1108,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1220,7 +1249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.6", "secp256k1-sys", "serde", ] @@ -1241,7 +1270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" dependencies = [ "bitcoin-private", - "rand", + "rand 0.8.6", "secp256k1", "secp256k1-zkp-sys", "serde", @@ -1393,6 +1422,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", + "rand_core 0.9.5", "serde", "simplicityhl", "syn", @@ -1454,6 +1484,7 @@ dependencies = [ "elements-miniscript", "hex", "minreq 3.0.0", + "rand_core 0.9.5", "serde", "serde_json", "sha2", @@ -1466,6 +1497,8 @@ name = "smplx-std" version = "0.0.7" dependencies = [ "either", + "rand 0.9.4", + "rand_core 0.9.5", "serde", "simplicityhl", "smplx-macros", diff --git a/Cargo.toml b/Cargo.toml index 1f67a9c4..60dc3bd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ smplx-regtest = { path = "./crates/regtest", version = "0.0.7" } smplx-sdk = { path = "./crates/sdk", version = "0.0.7" } smplx-std = { path = "./crates/simplex", version = "0.0.7" } +rand_core = { version = "0.9.5" } +rand = { version = "0.9.4" } serde = { version = "1.0.228", features = ["derive"] } hex = { version = "0.4.3" } hmac = { version = "0.12.1" } diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index 9ad9601e..452d9a84 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -13,11 +13,12 @@ thiserror = { workspace = true } toml = { workspace = true } serde = { workspace = true } simplicityhl = { workspace = true } +rand_core = { workspace = true } syn = { version = "2.0.114", default-features = false, features = ["proc-macro", "full", "parsing", "derive", "clone-impls", "extra-traits", "printing"] } proc-macro2 = { version = "1.0.106", features = ["span-locations"] } quote = { version = "1.0.44" } pathdiff = { version = "0.2.3" } prettyplease = { version = "0.2.37" } -glob = { version = "0.3.3"} -globwalk = { version = "0.9.1"} +glob = { version = "0.3.3" } +globwalk = { version = "0.9.1" } diff --git a/crates/build/src/macros/codegen.rs b/crates/build/src/macros/codegen.rs index 2936e9be..5525c88d 100644 --- a/crates/build/src/macros/codegen.rs +++ b/crates/build/src/macros/codegen.rs @@ -105,6 +105,7 @@ impl WitnessStruct { proc_macro2::TokenStream, proc_macro2::TokenStream, ) = self.generate_from_args_conversion_with_param_name("args"); + let rand_mapping: proc_macro2::TokenStream = self.generate_rand_mapping(); let default_mapping: proc_macro2::TokenStream = self.generate_default_mapping(); Ok(GeneratedArgumentTokens { @@ -117,6 +118,8 @@ impl WitnessStruct { use simplex::simplicityhl::types::TypeConstructible; use simplex::simplicityhl::value::ValueConstructible; use simplex::program::ArgumentsTrait; + use simplex::rand_core::{RngCore}; + use simplex::rand::Rng; }, struct_token_stream: quote! { #generated_struct @@ -134,6 +137,11 @@ impl WitnessStruct { Ok(#struct_to_return) } + /// Generate a random Arguments struct instance using the provided RNG. + pub fn generate_arguments_raw(rng: &mut R) -> Self + { + #rand_mapping + } } impl simplex::program::ArgumentsTrait for #struct_name { @@ -165,6 +173,13 @@ impl WitnessStruct { } } + impl simplex::program::RandomArguments for #struct_name { + fn generate_arguments(rng: &mut dyn RngCore) -> Arguments + { + Self::generate_arguments_raw(rng).build_arguments() + } + } + impl core::default::Default for #struct_name { fn default() -> Self { #default_mapping @@ -187,6 +202,7 @@ impl WitnessStruct { proc_macro2::TokenStream, ) = self.generate_from_args_conversion_with_param_name("witness"); let default_mapping: proc_macro2::TokenStream = self.generate_default_mapping(); + let rand_mapping: proc_macro2::TokenStream = self.generate_rand_mapping(); Ok(GeneratedWitnessTokens { imports: quote! { @@ -198,6 +214,8 @@ impl WitnessStruct { use simplex::simplicityhl::types::TypeConstructible; use simplex::simplicityhl::value::ValueConstructible; use simplex::program::WitnessTrait; + use simplex::rand_core::{RngCore}; + use simplex::rand::Rng; }, struct_token_stream: quote! { #generated_struct @@ -214,6 +232,12 @@ impl WitnessStruct { Ok(#struct_to_return) } + + /// Generate a random Witness struct instance using the provided RNG. + pub fn generate_witness_raw(rng: &mut R) -> Self + { + #rand_mapping + } } impl simplex::program::WitnessTrait for #struct_name { @@ -245,6 +269,13 @@ impl WitnessStruct { } } + impl simplex::program::RandomWitness for #struct_name { + fn generate_witness(rng: &mut dyn RngCore) -> simplex::simplicityhl::WitnessValues + { + Self::generate_witness_raw(rng).build_witness() + } + } + impl core::default::Default for #struct_name { fn default() -> Self { #default_mapping @@ -300,6 +331,24 @@ impl WitnessStruct { } } + fn generate_rand_mapping(&self) -> proc_macro2::TokenStream { + let name = format_ident!("{}", self.struct_name); + let fields: Vec = self + .witness_values + .iter() + .map(|field| { + let field_name = format_ident!("{}", field.struct_rust_field); + let field_default_value = field.rust_type.get_random_value(); + quote! { #field_name: #field_default_value } + }) + .collect(); + quote! { + #name { + #(#fields),* + } + } + } + fn generate_default_mapping(&self) -> proc_macro2::TokenStream { let name = format_ident!("{}", self.struct_name); let fields: Vec = self diff --git a/crates/build/src/macros/types.rs b/crates/build/src/macros/types.rs index 00fbcb68..fd44bc67 100644 --- a/crates/build/src/macros/types.rs +++ b/crates/build/src/macros/types.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use quote::quote; +use quote::{format_ident, quote}; use simplicityhl::ResolvedType; @@ -60,6 +60,61 @@ impl RustTypeContext { } impl RustType { + pub fn get_random_value(&self) -> proc_macro2::TokenStream { + match self { + RustType::Bool => quote! { rng.random() }, + RustType::U1 => quote! { rng.random::() as u8 }, + RustType::U2 => quote! { rng.random::() & 0x00_03 }, + RustType::U4 => quote! { rng.random::() & 0x00_0F }, + RustType::U8 | RustType::U16 | RustType::U32 | RustType::U64 | RustType::U128 => quote! { rng.random() }, + RustType::U256Array => quote! { rng.random() }, + RustType::Array(element, size) => { + let elements = vec![element.get_random_value(); *size]; + quote! { [#(#elements),*] } + } + RustType::Tuple(elements) => { + let element_values: Vec<_> = elements.iter().map(RustType::get_random_value).collect(); + quote! { (#(#element_values),*) } + } + RustType::Either(left, right) => { + let left_val = left.get_random_value(); + let right_val = right.get_random_value(); + quote! { if rng.random() { simplex::either::Either::Left(#left_val) } else { simplex::either::Either::Right(#right_val) } } + } + RustType::Option(inner) => { + let inner_val = inner.get_random_value(); + quote! { if rng.random() { Some(#inner_val) } else { None } } + } + RustType::List(element, size) => { + let (generated_fn, generated_fn_name) = { + let generated_fn_name = format_ident!("__gen_list_path"); + let rust_ret_type = element.to_type_token_stream(); + let rand_element_generation = element.get_random_value(); + + let generated_fn = quote! { + fn #generated_fn_name(rng: &mut R) -> #rust_ret_type{ + #rand_element_generation + } + }; + (generated_fn, generated_fn_name) + }; + + quote! { + { + #generated_fn + + let random_size: u32 = rng.random_range(0..#size as u32); + let mut res = Vec::with_capacity(random_size as usize); + for s in 0..random_size { + res.push(#generated_fn_name(rng)); + } + res + } + } + } + } + } + pub fn get_default_value(&self) -> proc_macro2::TokenStream { match self { RustType::Bool => quote! { Default::default() }, diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index fe86a18b..a2d6b24a 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -18,6 +18,7 @@ simplicityhl = { workspace = true } bitcoincore-rpc = { workspace = true } serde = { workspace = true } hex = { workspace = true } +rand_core = { workspace = true } serde_json = { version = "1.0" } elements-miniscript = { version = "0.4", features = ["base64", "serde"] } diff --git a/crates/sdk/src/program/arguments.rs b/crates/sdk/src/program/arguments.rs index 61d734dd..9c891ad9 100644 --- a/crates/sdk/src/program/arguments.rs +++ b/crates/sdk/src/program/arguments.rs @@ -2,10 +2,17 @@ use dyn_clone::DynClone; use simplicityhl::Arguments; /// An interface for structs capable of generating static argument mapping for Simplicity programs. -/// See the `include_simc!()` macro, which generates automatic `ArgumentsTrait` implementation. +/// See the `include_simf!()` macro, which generates automatic `ArgumentsTrait` implementation. pub trait ArgumentsTrait: DynClone { /// Compiles and returns the bound `Arguments` dict required to instantiate a program. fn build_arguments(&self) -> Arguments; } dyn_clone::clone_trait_object!(ArgumentsTrait); + +/// An interface for the struct capable of generating proper `Arguments` mappings using the provided RNG. +/// See the ` include_simf!()` macro, which generates an automatic `ArgumentsTrait` implementation. +pub trait RandomArguments: ArgumentsTrait { + /// Generates a random `Arguments` instance using the provided RNG. + fn generate_arguments(rng: &mut dyn rand_core::RngCore) -> Arguments; +} diff --git a/crates/sdk/src/program/mod.rs b/crates/sdk/src/program/mod.rs index 8722cf7e..a2bfc6eb 100644 --- a/crates/sdk/src/program/mod.rs +++ b/crates/sdk/src/program/mod.rs @@ -9,8 +9,8 @@ pub mod logger; /// Definitions and traits for resolving and satisfying execution witnesses for Simplicity programs. pub mod witness; -pub use arguments::ArgumentsTrait; +pub use arguments::{ArgumentsTrait, RandomArguments}; pub use core::{Program, ProgramTrait}; pub use error::ProgramError; pub use simplicityhl::tracker::TrackerLogLevel; -pub use witness::WitnessTrait; +pub use witness::{RandomWitness, WitnessTrait}; diff --git a/crates/sdk/src/program/witness.rs b/crates/sdk/src/program/witness.rs index 7bf47777..eaa451b0 100644 --- a/crates/sdk/src/program/witness.rs +++ b/crates/sdk/src/program/witness.rs @@ -2,10 +2,17 @@ use dyn_clone::DynClone; use simplicityhl::WitnessValues; /// An interface for structs capable of generating Simplicity program witness mappings. -/// See the ` include_simc!()` macro, which generates an automatic `WitnessTrait` implementation. +/// See the ` include_simf!()` macro, which generates an automatic `WitnessTrait` implementation. pub trait WitnessTrait: DynClone { /// Compiles and generates the fully populated `WitnessValues` map for execution. fn build_witness(&self) -> WitnessValues; } dyn_clone::clone_trait_object!(WitnessTrait); + +/// An interface for the struct capable of generating proper `WitnessValues` mappings using the provided RNG. +/// See the ` include_simf!()` macro, which generates an automatic `RandomWitness` implementation. +pub trait RandomWitness: WitnessTrait { + /// Generates a random `WitnessValues` instance using the provided RNG. + fn generate_witness(rng: &mut dyn rand_core::RngCore) -> WitnessValues; +} diff --git a/crates/simplex/Cargo.toml b/crates/simplex/Cargo.toml index 774b8842..50728385 100644 --- a/crates/simplex/Cargo.toml +++ b/crates/simplex/Cargo.toml @@ -24,6 +24,8 @@ smplx-sdk = { workspace = true } serde = { workspace = true } simplicityhl = { workspace = true, features = ["serde"] } +rand_core = { workspace = true } +rand = { workspace = true } either = { version = "1.15.0", features = ["serde"] } diff --git a/crates/simplex/src/lib.rs b/crates/simplex/src/lib.rs index 4e3525b9..a88873ec 100644 --- a/crates/simplex/src/lib.rs +++ b/crates/simplex/src/lib.rs @@ -1,4 +1,6 @@ pub use either; +pub use rand; +pub use rand_core; pub use serde; pub use simplicityhl; diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index 26ad3d9a..700da1e3 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, WitnessTrait}; +use simplex::program::{ArgumentsTrait, WitnessTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/array_tr_storage.simf"); @@ -16,5 +16,38 @@ fn main() -> Result<(), String> { let recovered_arguments = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = + derived_array_tr_storage::ArrayTrStorageWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = + derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_array_tr_storage::ArrayTrStorageWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = + derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = + derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs index de30a3f4..d44bbf1c 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf"); @@ -16,5 +16,38 @@ fn main() -> Result<(), String> { let recovered_arguments = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = + derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = + derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = + derived_bytes32_tr_storage::Bytes32TrStorageArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = + derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_bytes32_tr_storage::Bytes32TrStorageArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index a1395759..8cfc75ba 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/dual_currency_deposit.simf"); @@ -17,5 +17,38 @@ fn main() -> Result<(), String> { derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = + derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = + derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = + derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index 9f1186ef..49173804 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/either_with_single_witness.simf"); @@ -18,5 +18,38 @@ fn main() -> Result<(), String> { derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = + derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = + derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index e0aab6a7..c893f1ac 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/exotic_values.simf"); @@ -17,5 +17,32 @@ fn main() -> Result<(), String> { derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = derived_exotic_values::ExoticValuesWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_exotic_values::ExoticValuesWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = derived_exotic_values::ExoticValuesArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_exotic_values::ExoticValuesArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index a36079a0..d7943624 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/list_check.simf"); @@ -44,6 +44,33 @@ fn main() -> Result<(), String> { assert!(result.is_err(), "Expected build_witness to panic, as we have Vec size equal to list size, but it succeeded."); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = derived_list_check::ListCheckWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = derived_list_check::ListCheckWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_list_check::ListCheckWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = derived_list_check::ListCheckArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_list_check::ListCheckArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_list_check::ListCheckArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index 3ace0aeb..b2dd2e7f 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/option_offer.simf"); @@ -16,5 +16,32 @@ fn main() -> Result<(), String> { let recovered_arguments = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = derived_option_offer::OptionOfferWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_option_offer::OptionOfferWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = derived_option_offer::OptionOfferArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_option_offer::OptionOfferArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 9dad90af..31c62407 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/options.simf"); @@ -16,5 +16,32 @@ fn main() -> Result<(), String> { let recovered_arguments = derived_options::OptionsArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = derived_options::OptionsWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = derived_options::OptionsWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_options::OptionsWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = derived_options::OptionsArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_options::OptionsArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_options::OptionsArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + }; + Ok(()) } diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index 34147700..de159ee3 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/simple_storage.simf"); @@ -16,5 +16,32 @@ fn main() -> Result<(), String> { let recovered_arguments = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = derived_simple_storage::SimpleStorageWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_simple_storage::SimpleStorageWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = derived_simple_storage::SimpleStorageArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_simple_storage::SimpleStorageArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + } + Ok(()) } diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index d1397cc2..ca88220c 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; include_simf!("../../../../crates/simplex/tests/ui_simfs/single_bit.simf"); @@ -80,5 +80,32 @@ fn main() -> Result<(), String> { "Testing values with (FLAG = 0, BIT = 0) (Arguments)" ); + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + let original_witness = derived_single_bit::SingleBitWitness::generate_witness_raw(&mut rng); + + let witness_values = original_witness.build_witness(); + let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_single_bit::SingleBitWitness::generate_witness(&mut rng); + assert_eq!(witness_values, rand_raw_witness_values); + + rng = StdRng::seed_from_u64(seed); + let original_arguments = derived_single_bit::SingleBitArguments::generate_arguments_raw(&mut rng); + + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + + rng = StdRng::seed_from_u64(seed); + let rand_raw_witness_values = derived_single_bit::SingleBitArguments::generate_arguments(&mut rng); + assert_eq!(arguments_values, rand_raw_witness_values); + } + Ok(()) } diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 57022745..1e42366a 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -150,8 +150,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ "bitcoin_hashes", - "rand", - "rand_core", + "rand 0.8.6", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -981,8 +981,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -992,7 +1002,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1004,6 +1024,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1136,7 +1165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.6", "secp256k1-sys", "serde", ] @@ -1157,7 +1186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" dependencies = [ "bitcoin-private", - "rand", + "rand 0.8.6", "secp256k1", "secp256k1-zkp-sys", "serde", @@ -1317,6 +1346,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", + "rand_core 0.9.5", "serde", "simplicityhl", "syn", @@ -1358,6 +1388,7 @@ dependencies = [ "elements-miniscript", "hex", "minreq 3.0.0", + "rand_core 0.9.5", "serde", "serde_json", "sha2", @@ -1370,6 +1401,8 @@ name = "smplx-std" version = "0.0.7" dependencies = [ "either", + "rand 0.9.4", + "rand_core 0.9.5", "serde", "simplicityhl", "smplx-macros", diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index d9de5994..a7aac83f 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -150,8 +150,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ "bitcoin_hashes", - "rand", - "rand_core", + "rand 0.8.6", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -981,8 +981,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -992,7 +1002,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1004,6 +1024,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1136,7 +1165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand", + "rand 0.8.6", "secp256k1-sys", "serde", ] @@ -1157,7 +1186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" dependencies = [ "bitcoin-private", - "rand", + "rand 0.8.6", "secp256k1", "secp256k1-zkp-sys", "serde", @@ -1317,6 +1346,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", + "rand_core 0.9.5", "serde", "simplicityhl", "syn", @@ -1358,6 +1388,7 @@ dependencies = [ "elements-miniscript", "hex", "minreq 3.0.0", + "rand_core 0.9.5", "serde", "serde_json", "sha2", @@ -1370,6 +1401,8 @@ name = "smplx-std" version = "0.0.7" dependencies = [ "either", + "rand 0.9.4", + "rand_core 0.9.5", "serde", "simplicityhl", "smplx-macros", From e67d76f24e34d5de39b57ad34350fe2888bd31a8 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 9 Jun 2026 10:38:05 +0300 Subject: [PATCH 02/21] simplex: move ui tests code into separate functions --- crates/simplex/tests/ui/array_tr_storage.rs | 26 ++++- crates/simplex/tests/ui/bytes32_tr_storage.rs | 35 +++++- .../simplex/tests/ui/dual_currency_deposit.rs | 56 +++++++-- .../tests/ui/either_with_single_witness.rs | 67 ++++++++--- crates/simplex/tests/ui/exotic_values.rs | 44 +++++-- crates/simplex/tests/ui/list_check.rs | 46 ++++++-- crates/simplex/tests/ui/option_offer.rs | 53 +++++++-- crates/simplex/tests/ui/options.rs | 47 ++++++-- crates/simplex/tests/ui/simple_storage.rs | 51 ++++++-- crates/simplex/tests/ui/single_bit.rs | 110 +++++++----------- 10 files changed, 389 insertions(+), 146 deletions(-) diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index 700da1e3..367f85ee 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -4,6 +4,14 @@ use simplex::program::{ArgumentsTrait, WitnessTrait, RandomArguments, RandomWitn include_simf!("../../../../crates/simplex/tests/ui_simfs/array_tr_storage.simf"); fn main() -> Result<(), String> { + test_e2e_behaviour()?; + test_default()?; + test_e2e_random_behaviour()?; + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_array_tr_storage::ArrayTrStorageWitness::default(); let witness_values = original_witness.build_witness(); @@ -16,6 +24,22 @@ fn main() -> Result<(), String> { let recovered_arguments = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_array_tr_storage::ArrayTrStorageWitness::default(), + derived_array_tr_storage::ArrayTrStorageWitness::default() + ); + assert_eq!( + derived_array_tr_storage::ArrayTrStorageArguments::default(), + derived_array_tr_storage::ArrayTrStorageArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String>{ for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; @@ -50,4 +74,4 @@ fn main() -> Result<(), String> { }; Ok(()) -} +} \ No newline at end of file diff --git a/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs index d44bbf1c..5bb6d5a4 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,21 +1,47 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_bytes32_tr_storage::Bytes32TrStorageArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_bytes32_tr_storage::Bytes32TrStorageWitness::default(), + derived_bytes32_tr_storage::Bytes32TrStorageWitness::default() + ); + assert_eq!( + derived_bytes32_tr_storage::Bytes32TrStorageArguments::default(), + derived_bytes32_tr_storage::Bytes32TrStorageArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; @@ -47,7 +73,6 @@ fn main() -> Result<(), String> { let rand_raw_witness_values = derived_bytes32_tr_storage::Bytes32TrStorageArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); - }; - + } Ok(()) } diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index 8cfc75ba..96896c44 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,33 +1,64 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/dual_currency_deposit.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_dual_currency_deposit::DualCurrencyDepositWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_dual_currency_deposit::DualCurrencyDepositArguments::default(); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; + derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments( + &arguments_values, + )?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_dual_currency_deposit::DualCurrencyDepositWitness::default(), + derived_dual_currency_deposit::DualCurrencyDepositWitness::default() + ); + assert_eq!( + derived_dual_currency_deposit::DualCurrencyDepositArguments::default(), + derived_dual_currency_deposit::DualCurrencyDepositArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; let mut rng = StdRng::seed_from_u64(seed); let original_witness = - derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness_raw(&mut rng); + derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness_raw( + &mut rng, + ); let witness_values = original_witness.build_witness(); let recovered_witness = - derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; + derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness( + &witness_values, + )?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); @@ -37,18 +68,23 @@ fn main() -> Result<(), String> { rng = StdRng::seed_from_u64(seed); let original_arguments = - derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments_raw(&mut rng); + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments_raw( + &mut rng, + ); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; + derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments( + &arguments_values, + )?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = - derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments(&mut rng); + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments( + &mut rng, + ); assert_eq!(arguments_values, rand_raw_witness_values); - }; - + } Ok(()) } diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index 49173804..95bbed3d 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,55 +1,96 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/either_with_single_witness.simf"); fn main() -> Result<(), String> { - let original_witness = derived_either_with_single_witness::EitherWithSingleWitnessWitness::default(); + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { + let original_witness = + derived_either_with_single_witness::EitherWithSingleWitnessWitness::default(); let witness_values = original_witness.build_witness(); let recovered_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; + derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness( + &witness_values, + )?; assert_eq!(original_witness, recovered_witness); - let original_arguments = derived_either_with_single_witness::EitherWithSingleWitnessArguments::default(); + let original_arguments = + derived_either_with_single_witness::EitherWithSingleWitnessArguments::default(); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments(&arguments_values)?; + derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( + &arguments_values, + )?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_either_with_single_witness::EitherWithSingleWitnessWitness::default(), + derived_either_with_single_witness::EitherWithSingleWitnessWitness::default() + ); + assert_eq!( + derived_either_with_single_witness::EitherWithSingleWitnessArguments::default(), + derived_either_with_single_witness::EitherWithSingleWitnessArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; let mut rng = StdRng::seed_from_u64(seed); let original_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness_raw(&mut rng); + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness_raw( + &mut rng, + ); let witness_values = original_witness.build_witness(); let recovered_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; + derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness( + &witness_values, + )?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness(&mut rng); + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness( + &mut rng, + ); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); let original_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments_raw(&mut rng); + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments_raw( + &mut rng, + ); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments(&arguments_values)?; + derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( + &arguments_values, + )?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments(&mut rng); + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments( + &mut rng, + ); assert_eq!(arguments_values, rand_raw_witness_values); - }; - + } Ok(()) } diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index c893f1ac..fd92a0a0 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,9 +1,17 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/exotic_values.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_exotic_values::ExoticValuesWitness::default(); let witness_values = original_witness.build_witness(); @@ -17,6 +25,22 @@ fn main() -> Result<(), String> { derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_exotic_values::ExoticValuesWitness::default(), + derived_exotic_values::ExoticValuesWitness::default() + ); + assert_eq!( + derived_exotic_values::ExoticValuesArguments::default(), + derived_exotic_values::ExoticValuesArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; @@ -25,24 +49,28 @@ fn main() -> Result<(), String> { let original_witness = derived_exotic_values::ExoticValuesWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_exotic_values::ExoticValuesWitness::generate_witness(&mut rng); + let rand_raw_witness_values = + derived_exotic_values::ExoticValuesWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = derived_exotic_values::ExoticValuesArguments::generate_arguments_raw(&mut rng); + let original_arguments = + derived_exotic_values::ExoticValuesArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_exotic_values::ExoticValuesArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = + derived_exotic_values::ExoticValuesArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); - }; - + } Ok(()) } diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index d7943624..08d24aa3 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,9 +1,18 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/list_check.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_build_panic()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_list_check::ListCheckWitness { draft: vec![], path: simplex::either::Left(vec![ @@ -17,12 +26,16 @@ fn main() -> Result<(), String> { let recovered_witness = derived_list_check::ListCheckWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); - let original_arguments = derived_list_check::ListCheckArguments {}; + let original_arguments = derived_list_check::ListCheckArguments::default(); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = derived_list_check::ListCheckArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_build_panic() -> Result<(), String> { // Build Witness, which would panic on building let original_witness = derived_list_check::ListCheckWitness { draft: vec![], @@ -37,13 +50,31 @@ fn main() -> Result<(), String> { // Register panic hook to reduce warnings let default_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(|_| {})); - let result = std::panic::catch_unwind(|| { - original_witness.build_witness() - }); + let result = std::panic::catch_unwind(|| original_witness.build_witness()); std::panic::set_hook(default_hook); - assert!(result.is_err(), "Expected build_witness to panic, as we have Vec size equal to list size, but it succeeded."); + assert!( + result.is_err(), + "Expected build_witness to panic, as we have Vec size equal to list size, but it succeeded." + ); + + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_list_check::ListCheckWitness::default(), + derived_list_check::ListCheckWitness::default() + ); + assert_eq!( + derived_list_check::ListCheckArguments::default(), + derived_list_check::ListCheckArguments::default() + ); + + Ok(()) +} +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; @@ -69,8 +100,7 @@ fn main() -> Result<(), String> { rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = derived_list_check::ListCheckArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); - }; + } Ok(()) } - diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index b2dd2e7f..40107391 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,47 +1,78 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/option_offer.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_option_offer::OptionOfferWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_option_offer::OptionOfferArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_option_offer::OptionOfferWitness::default(), + derived_option_offer::OptionOfferWitness::default() + ); + assert_eq!( + derived_option_offer::OptionOfferArguments::default(), + derived_option_offer::OptionOfferArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; let mut rng = StdRng::seed_from_u64(seed); - let original_witness = derived_option_offer::OptionOfferWitness::generate_witness_raw(&mut rng); + let original_witness = + derived_option_offer::OptionOfferWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_option_offer::OptionOfferWitness::generate_witness(&mut rng); + let rand_raw_witness_values = + derived_option_offer::OptionOfferWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = derived_option_offer::OptionOfferArguments::generate_arguments_raw(&mut rng); + let original_arguments = + derived_option_offer::OptionOfferArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_option_offer::OptionOfferArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = + derived_option_offer::OptionOfferArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); - }; - + } Ok(()) } diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 31c62407..9a733318 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,21 +1,47 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/options.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_options::OptionsWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_options::OptionsWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_options::OptionsWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_options::OptionsArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_options::OptionsArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_options::OptionsArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_options::OptionsWitness::default(), + derived_options::OptionsWitness::default() + ); + assert_eq!( + derived_options::OptionsArguments::default(), + derived_options::OptionsArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; @@ -24,7 +50,8 @@ fn main() -> Result<(), String> { let original_witness = derived_options::OptionsWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_options::OptionsWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_options::OptionsWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); @@ -32,16 +59,18 @@ fn main() -> Result<(), String> { assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = derived_options::OptionsArguments::generate_arguments_raw(&mut rng); + let original_arguments = + derived_options::OptionsArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_options::OptionsArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_options::OptionsArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_options::OptionsArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = + derived_options::OptionsArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); - }; - + } Ok(()) } diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index de159ee3..5d5fa88b 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,47 +1,78 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/simple_storage.simf"); fn main() -> Result<(), String> { + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); + + Ok(()) +} + +fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_simple_storage::SimpleStorageWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_simple_storage::SimpleStorageArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); + Ok(()) +} + +fn test_default() -> Result<(), String> { + assert_eq!( + derived_simple_storage::SimpleStorageWitness::default(), + derived_simple_storage::SimpleStorageWitness::default() + ); + assert_eq!( + derived_simple_storage::SimpleStorageArguments::default(), + derived_simple_storage::SimpleStorageArguments::default() + ); + Ok(()) +} + +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; let mut rng = StdRng::seed_from_u64(seed); - let original_witness = derived_simple_storage::SimpleStorageWitness::generate_witness_raw(&mut rng); + let original_witness = + derived_simple_storage::SimpleStorageWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_simple_storage::SimpleStorageWitness::generate_witness(&mut rng); + let rand_raw_witness_values = + derived_simple_storage::SimpleStorageWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = derived_simple_storage::SimpleStorageArguments::generate_arguments_raw(&mut rng); + let original_arguments = + derived_simple_storage::SimpleStorageArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_simple_storage::SimpleStorageArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = + derived_simple_storage::SimpleStorageArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } - Ok(()) } diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index ca88220c..73ddad0c 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,85 +1,49 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait, RandomArguments, RandomWitness}; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; include_simf!("../../../../crates/simplex/tests/ui_simfs/single_bit.simf"); fn main() -> Result<(), String> { - // Testing values with (FLAG = 1, BIT = 1) - let original_witness = derived_single_bit::SingleBitWitness { bit: 1 }; + let _ = test_e2e_behaviour()?; + let _ = test_default(); + let _ = test_e2e_random_behaviour(); - let witness_values = original_witness.build_witness(); - let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; - assert_eq!( - original_witness, recovered_witness, - "Testing values with (FLAG = 1, BIT = 1) (Witness)" - ); - - let original_arguments = derived_single_bit::SingleBitArguments { flag: 1 }; - - let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; - assert_eq!( - original_arguments, recovered_arguments, - "Testing values with (FLAG = 1, BIT = 1) (Arguments)" - ); - - // Testing values with (FLAG = 0, BIT = 1) - let original_witness = derived_single_bit::SingleBitWitness { bit: 1 }; - - let witness_values = original_witness.build_witness(); - let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; - assert_eq!( - original_witness, recovered_witness, - "Testing values with (FLAG = 0, BIT = 1) (Witness)" - ); - - let original_arguments = derived_single_bit::SingleBitArguments { flag: 0 }; - - let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; - assert_eq!( - original_arguments, recovered_arguments, - "Testing values with (FLAG = 0, BIT = 1) (Arguments)" - ); + Ok(()) +} - // Testing values with (FLAG = 1, BIT = 0) - let original_witness = derived_single_bit::SingleBitWitness { bit: 0 }; +fn test_e2e_behaviour() -> Result<(), String> { + for (bit, flag) in [(1, 1), (1, 0), (0, 1), (0, 0)] { + let original_witness = derived_single_bit::SingleBitWitness { bit }; - let witness_values = original_witness.build_witness(); - let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; - assert_eq!( - original_witness, recovered_witness, - "Testing values with (FLAG = 1, BIT = 0) (Witness)" - ); + let witness_values = original_witness.build_witness(); + let recovered_witness = + derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + assert_eq!(original_witness, recovered_witness); - let original_arguments = derived_single_bit::SingleBitArguments { flag: 1 }; + let original_arguments = derived_single_bit::SingleBitArguments { flag }; - let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; - assert_eq!( - original_arguments, recovered_arguments, - "Testing values with (FLAG = 1, BIT = 0) (Arguments)" - ); + let arguments_values = original_arguments.build_arguments(); + let recovered_arguments = + derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + } - // Testing values with (FLAG = 0, BIT = 0) - let original_witness = derived_single_bit::SingleBitWitness { bit: 0 }; + Ok(()) +} - let witness_values = original_witness.build_witness(); - let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; +fn test_default() -> Result<(), String> { assert_eq!( - original_witness, recovered_witness, - "Testing values with (FLAG = 0, BIT = 0) (Witness)" + derived_single_bit::SingleBitWitness::default(), + derived_single_bit::SingleBitWitness::default() ); - - let original_arguments = derived_single_bit::SingleBitArguments { flag: 0 }; - - let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; assert_eq!( - original_arguments, recovered_arguments, - "Testing values with (FLAG = 0, BIT = 0) (Arguments)" + derived_single_bit::SingleBitArguments::default(), + derived_single_bit::SingleBitArguments::default() ); + Ok(()) +} +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; @@ -88,24 +52,28 @@ fn main() -> Result<(), String> { let original_witness = derived_single_bit::SingleBitWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + let recovered_witness = + derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_single_bit::SingleBitWitness::generate_witness(&mut rng); + let rand_raw_witness_values = + derived_single_bit::SingleBitWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = derived_single_bit::SingleBitArguments::generate_arguments_raw(&mut rng); + let original_arguments = + derived_single_bit::SingleBitArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + let recovered_arguments = + derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = derived_single_bit::SingleBitArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = + derived_single_bit::SingleBitArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } - Ok(()) } From 3d2409200106cffcd78cce106f50ba0ae65e1de5 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 10 Jun 2026 12:23:44 +0300 Subject: [PATCH 03/21] feat: add mutantesting (draft) * update macros (add From implementations and other helpers for proptests) * update ui tests * update interface of program creation (change ArgumentsTrait into the Into) * add 2 property tests in fixtures/tests/prop_testing.rs --- Cargo.lock | 92 ++++++++ crates/build/src/generator.rs | 16 +- crates/build/src/macros/codegen.rs | 81 ++++++- crates/build/src/macros/core.rs | 19 +- crates/sdk/src/program/core.rs | 56 ++++- crates/sdk/src/program/mod.rs | 2 +- crates/sdk/src/signer/core.rs | 4 +- crates/sdk/src/transaction/partial_input.rs | 13 +- crates/simplex/src/lib.rs | 1 + crates/simplex/tests/ui/array_tr_storage.rs | 62 +++++ crates/simplex/tests/ui/bytes32_tr_storage.rs | 62 +++++ .../simplex/tests/ui/dual_currency_deposit.rs | 62 +++++ .../tests/ui/either_with_single_witness.rs | 62 +++++ crates/simplex/tests/ui/exotic_values.rs | 62 +++++ crates/simplex/tests/ui/list_check.rs | 62 +++++ crates/simplex/tests/ui/option_offer.rs | 62 +++++ crates/simplex/tests/ui/options.rs | 62 +++++ crates/simplex/tests/ui/simple_storage.rs | 62 +++++ crates/simplex/tests/ui/single_bit.rs | 62 +++++ crates/test/Cargo.toml | 2 + crates/test/src/lib.rs | 2 + crates/test/src/mutantesting/core.rs | 59 +++++ crates/test/src/mutantesting/engine.rs | 137 +++++++++++ crates/test/src/mutantesting/mod.rs | 8 + crates/test/src/mutantesting/provider.rs | 64 +++++ crates/test/src/mutantesting/strategy.rs | 2 + crates/test/src/mutantesting/strategy/args.rs | 219 ++++++++++++++++++ crates/test/src/mutantesting/strategy/pset.rs | 67 ++++++ examples/basic/Cargo.lock | 92 ++++++++ examples/basic/tests/basic_test.rs | 2 +- fixtures/Cargo.lock | 92 ++++++++ fixtures/simf/failure_test.simf | 13 ++ fixtures/tests/basic_test.rs | 2 +- fixtures/tests/log_level.rs | 2 +- fixtures/tests/multidep_test.rs | 2 +- fixtures/tests/nested_sig.rs | 2 +- fixtures/tests/prop_testing.rs | 81 +++++++ 37 files changed, 1716 insertions(+), 36 deletions(-) create mode 100644 crates/test/src/mutantesting/core.rs create mode 100644 crates/test/src/mutantesting/engine.rs create mode 100644 crates/test/src/mutantesting/mod.rs create mode 100644 crates/test/src/mutantesting/provider.rs create mode 100644 crates/test/src/mutantesting/strategy.rs create mode 100644 crates/test/src/mutantesting/strategy/args.rs create mode 100644 crates/test/src/mutantesting/strategy/pset.rs create mode 100644 fixtures/simf/failure_test.simf create mode 100644 fixtures/tests/prop_testing.rs diff --git a/Cargo.lock b/Cargo.lock index 1c039a3a..65908505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitcoin" version = "0.32.100" @@ -571,6 +586,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -945,6 +966,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "objc2" version = "0.6.4" @@ -1027,6 +1057,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.0", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax 0.8.10", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "psm" version = "0.1.31" @@ -1037,6 +1086,12 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.45" @@ -1117,6 +1172,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1233,6 +1297,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1513,6 +1589,7 @@ version = "0.0.7" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", "serde", "simplicityhl", @@ -1719,6 +1796,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1764,6 +1847,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index 8b642382..b63aefa7 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -16,8 +16,7 @@ use simplicityhl::source::CanonPath; use simplicityhl::source::CanonSourceFile; use crate::macros::codegen::{ - convert_contract_name_to_contract_module, convert_contract_name_to_contract_source_const, - convert_contract_name_to_struct_name, + construct_program_name, convert_contract_name_to_contract_module, convert_contract_name_to_contract_source_const, }; use crate::macros::parse::SimfContent; @@ -241,22 +240,19 @@ impl ArtifactsGenerator { } fn generate_simf_binding_code(contract_name: &str, target_simf: &Path) -> Result { - let program_name = { - let base_name = convert_contract_name_to_struct_name(contract_name); - format_ident!("{base_name}Program") - }; - + let program_name = construct_program_name(contract_name); let include_simf_source_const = convert_contract_name_to_contract_source_const(contract_name); let include_simf_module = convert_contract_name_to_contract_module(contract_name); let target_simf_str = target_simf.to_string_lossy().into_owned(); let code = quote! { use simplex::include_simf; - use simplex::program::{ArgumentsTrait, Program}; + use simplex::program::{Program}; use simplex::provider::SimplicityNetwork; use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; + #[derive(Clone)] pub struct #program_name { program: Program, } @@ -265,9 +261,9 @@ impl ArtifactsGenerator { pub const SOURCE: &'static str = #include_simf_module::#include_simf_source_const; #[must_use] - pub fn new(arguments: impl ArgumentsTrait + 'static) -> Self { + pub fn new(arguments: impl Into) -> Self { Self { - program: Program::new(Self::SOURCE, Box::new(arguments)), + program: Program::new(Self::SOURCE, arguments.into()), } } diff --git a/crates/build/src/macros/codegen.rs b/crates/build/src/macros/codegen.rs index 5525c88d..2de8d814 100644 --- a/crates/build/src/macros/codegen.rs +++ b/crates/build/src/macros/codegen.rs @@ -8,6 +8,7 @@ use crate::macros::types::RustType; pub struct SimfContractMeta { pub contract_source_const_name: proc_macro2::Ident, + pub program_struct_name: proc_macro2::Ident, pub args_struct: WitnessStruct, pub witness_struct: WitnessStruct, pub simf_content: SimfContent, @@ -26,6 +27,11 @@ pub struct GeneratedWitnessTokens { pub struct_impl: proc_macro2::TokenStream, } +pub struct GeneratedMutanTestingTokens { + pub imports: proc_macro2::TokenStream, + pub helper_impls: proc_macro2::TokenStream, +} + pub struct WitnessField { witness_simf_name: String, struct_rust_field: proc_macro2::Ident, @@ -47,15 +53,63 @@ impl SimfContractMeta { let witness_struct = WitnessStruct::generate_witness_struct(&simf_content.contract_name, &abi_meta.witness_types)?; let contract_source_const_name = convert_contract_name_to_contract_source_const(&simf_content.contract_name); - + let program_struct_name = construct_program_name(&simf_content.contract_name); Ok(SimfContractMeta { contract_source_const_name, + program_struct_name, args_struct, witness_struct, simf_content, abi_meta, }) } + + /// Generates code necessary for creating mutant testing using simplex. + pub fn generate_mutantesting_impl(&self) -> syn::Result { + let args_struct_name = &self.args_struct.struct_name; + let program_name = &self.program_struct_name; + + let fuzzable_program_impl = quote! { + impl FuzzableProgram<#program_name> for #program_name { + fn build_program( + args: impl Into, + network: &SimplicityNetwork, + ) -> (Box<#program_name>, Script) { + let prog = #program_name::new(args); + let script = prog.get_script_pubkey(network); + (Box::new(prog), script) + } + } + + impl SimplexProgram for #program_name { + fn get_program(&self) -> &Program { + &self.program + } + + fn get_compiled_program(args: Arguments) -> CompiledProgram { + #program_name::new(args).program.load().unwrap() + } + + fn get_mut_program(&mut self) -> &mut Program { + &mut self.program + } + } + }; + + Ok(GeneratedMutanTestingTokens { + imports: quote! { + use super::{super::#program_name, #args_struct_name}; + use simplex::mutantesting::FuzzableProgram; + use simplex::provider::SimplicityNetwork; + use simplex::program::{Program, SimplexProgram}; + use simplex::simplicityhl::{Arguments, CompiledProgram}; + use simplex::simplicityhl::elements::Script; + }, + helper_impls: quote! { + #fuzzable_program_impl + }, + }) + } } impl WitnessField { @@ -126,11 +180,11 @@ impl WitnessStruct { }, struct_impl: quote! { impl #struct_name { - /// Build struct from Simplicity Arguments. + /// Build struct from Simplicity `Arguments`. /// /// # Errors /// - /// Returns error if any required witness is missing, has wrong type, or has invalid value. + /// Returns error if any required witness is missing, has the wrong type, or has an invalid value. pub fn from_arguments(args: &Arguments) -> Result { #arguments_conversion_from_args_map @@ -174,7 +228,7 @@ impl WitnessStruct { } impl simplex::program::RandomArguments for #struct_name { - fn generate_arguments(rng: &mut dyn RngCore) -> Arguments + fn generate_arguments(rng: &mut dyn RngCore) -> simplex::simplicityhl::Arguments { Self::generate_arguments_raw(rng).build_arguments() } @@ -185,6 +239,12 @@ impl WitnessStruct { #default_mapping } } + + impl From<#struct_name> for simplex::simplicityhl::Arguments { + fn from(val: #struct_name) -> simplex::simplicityhl::Arguments { + val.build_arguments() + } + } }, }) } @@ -222,7 +282,7 @@ impl WitnessStruct { }, struct_impl: quote! { impl #struct_name { - /// Build struct from Simplicity WitnessValues. + /// Build struct from Simplicity `WitnessValues`. /// /// # Errors /// @@ -281,6 +341,12 @@ impl WitnessStruct { #default_mapping } } + + impl From<#struct_name> for simplex::simplicityhl::WitnessValues { + fn from(val: #struct_name) -> simplex::simplicityhl::WitnessValues { + val.build_witness() + } + } }, }) } @@ -416,6 +482,11 @@ impl WitnessStruct { } } +pub fn construct_program_name(contract_name: &str) -> proc_macro2::Ident { + let base_name = convert_contract_name_to_struct_name(contract_name); + format_ident!("{base_name}Program") +} + pub fn convert_contract_name_to_struct_name(contract_name: &str) -> String { let words: Vec = contract_name .split('_') diff --git a/crates/build/src/macros/core.rs b/crates/build/src/macros/core.rs index 12f6d5b5..2e028179 100644 --- a/crates/build/src/macros/core.rs +++ b/crates/build/src/macros/core.rs @@ -7,7 +7,8 @@ use simplicityhl::ast::ElementsJetHinter; use simplicityhl::{AbiMeta, TemplateProgram, UnstableFeatures}; use super::codegen::{ - GeneratedArgumentTokens, GeneratedWitnessTokens, SimfContractMeta, convert_contract_name_to_contract_module, + GeneratedArgumentTokens, GeneratedMutanTestingTokens, GeneratedWitnessTokens, SimfContractMeta, + convert_contract_name_to_contract_module, }; use super::parse::{SimfContent, SynFilePath}; @@ -27,6 +28,7 @@ fn expand_inner(simf_content: SimfContent, meta: AbiMeta) -> Result Result Result> { .generate_abi_meta()?, ) } + +fn construct_mutantesting_helpers(derived_meta: &SimfContractMeta) -> syn::Result { + let GeneratedMutanTestingTokens { imports, helper_impls } = derived_meta.generate_mutantesting_impl()?; + + // TODO: move this feature under flag if is needed + Ok(quote! { + mod mutantesting { + #imports + + #helper_impls + } + }) +} diff --git a/crates/sdk/src/program/core.rs b/crates/sdk/src/program/core.rs index d683f359..ea0d965d 100644 --- a/crates/sdk/src/program/core.rs +++ b/crates/sdk/src/program/core.rs @@ -9,13 +9,12 @@ use simplicityhl::elements::{Address, Script, Transaction, TxOut, taproot}; use simplicityhl::simplicity::bitcoin::{XOnlyPublicKey, secp256k1}; use simplicityhl::simplicity::jet::elements::{ElementsEnv, ElementsUtxo}; use simplicityhl::simplicity::{BitMachine, RedeemNode, Value, leaf_version}; -use simplicityhl::{CompiledProgram, UnstableFeatures}; +use simplicityhl::{Arguments, CompiledProgram, UnstableFeatures}; use simplicityhl::{Parameters, WitnessTypes, WitnessValues}; use crate::global::GlobalConfig; use crate::program::logger::ProgramLogger; -use super::arguments::ArgumentsTrait; use super::error::ProgramError; use crate::provider::SimplicityNetwork; @@ -77,6 +76,29 @@ pub trait ProgramTrait: DynClone { ) -> Result>, ProgramError>; } +/// A trait that defines and additional functionality required for handling a Simplicity-based +/// program for a `Program` value wrapper generated by `include_simf!` macro. +pub trait SimplexProgram { + /// Generates the script public key (scriptPubKey) associated with the Simplicity program. + fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.get_program().get_script_pubkey(network) + } + + /// Computes the script hash for the current program based on the provided network. + fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.get_program().get_script_hash(network) + } + + /// Retrieves a reference to the program associated with the current instance. + fn get_program(&self) -> &Program; + + /// Compiles the provided program based on the given arguments and returns the compiled program. + fn get_compiled_program(args: Arguments) -> CompiledProgram; + + /// Returns a mutable reference to the `Program` instance. + fn get_mut_program(&mut self) -> &mut Program; +} + /// Represents a program structure containing its source, a public key, arguments, and associated storage. /// /// Abstraction giving the power to execute Simplicity contracts without specifying any additional parameters. @@ -84,7 +106,7 @@ pub trait ProgramTrait: DynClone { pub struct Program { source: &'static str, pub_key: XOnlyPublicKey, - arguments: Box, + arguments: Arc, storage: Vec<[u8; 32]>, } @@ -199,11 +221,17 @@ impl ProgramTrait for Program { impl Program { /// Creates a new instance of the struct with the provided source string and arguments. #[must_use] - pub fn new(source: &'static str, arguments: Box) -> Self { + pub fn new(source: &'static str, arguments: impl Into) -> Self { + Self::new_from_arguments(source, arguments.into()) + } + + /// Createst a new instance of `Program` by providing raw `Arguments`. + #[must_use] + fn new_from_arguments(source: &'static str, arguments: Arguments) -> Self { Self { source, pub_key: tr_unspendable_key(), - arguments, + arguments: Arc::new(arguments), storage: Vec::new(), } } @@ -307,11 +335,15 @@ impl Program { Ok(abi_meta.witness_types) } - fn load(&self) -> Result { + /// Compiles program by providing saved `Arguments`. + /// + /// # Errors + /// Retruns an error we have problems in a program compilation. + pub fn load(&self) -> Result { let compiled = CompiledProgram::new_with_unstable( self.source, &UnstableFeatures::all(), - self.arguments.build_arguments(), + (*self.arguments).clone(), GlobalConfig::get_include_debug_symbols(), Box::new(ElementsJetHinter), ) @@ -383,6 +415,8 @@ mod tests { use super::*; + use crate::program::ArgumentsTrait; + // simplicityhl/examples/cat.simf const DUMMY_PROGRAM: &str = r" fn main() { @@ -404,12 +438,18 @@ mod tests { } } + impl From for Arguments { + fn from(val: EmptyArguments) -> Self { + val.build_arguments() + } + } + fn dummy_asset_id(byte: u8) -> AssetId { AssetId::from_slice(&[byte; 32]).unwrap() } fn dummy_program() -> Program { - Program::new(DUMMY_PROGRAM, Box::new(EmptyArguments)) + Program::new(DUMMY_PROGRAM, EmptyArguments {}) } fn dummy_network() -> SimplicityNetwork { diff --git a/crates/sdk/src/program/mod.rs b/crates/sdk/src/program/mod.rs index a2bfc6eb..da9927a6 100644 --- a/crates/sdk/src/program/mod.rs +++ b/crates/sdk/src/program/mod.rs @@ -10,7 +10,7 @@ pub mod logger; pub mod witness; pub use arguments::{ArgumentsTrait, RandomArguments}; -pub use core::{Program, ProgramTrait}; +pub use core::{Program, ProgramTrait, SimplexProgram}; pub use error::ProgramError; pub use simplicityhl::tracker::TrackerLogLevel; pub use witness::{RandomWitness, WitnessTrait}; diff --git a/crates/sdk/src/signer/core.rs b/crates/sdk/src/signer/core.rs index dc53b4d2..c4d6acb0 100644 --- a/crates/sdk/src/signer/core.rs +++ b/crates/sdk/src/signer/core.rs @@ -511,13 +511,13 @@ impl Signer { Some((witness_name, sig_path)) => Ok(self.get_signed_program_witness( &pst, program_input.program.as_ref(), - &program_input.witness.build_witness(), + &program_input.witness, witness_name, sig_path, index, )?), // just build the witness - None => Ok(program_input.witness.build_witness()), + None => Ok((*program_input.witness).clone()), }; let pruned_witness = diff --git a/crates/sdk/src/transaction/partial_input.rs b/crates/sdk/src/transaction/partial_input.rs index 88b6235c..30cdf102 100644 --- a/crates/sdk/src/transaction/partial_input.rs +++ b/crates/sdk/src/transaction/partial_input.rs @@ -1,9 +1,11 @@ +use std::sync::Arc; + +use simplicityhl::WitnessValues; use simplicityhl::elements::confidential::{Asset, Value}; use simplicityhl::elements::pset::Input; use simplicityhl::elements::{AssetId, LockTime, OutPoint, Sequence, TxOut, TxOutSecrets, Txid}; use crate::program::ProgramTrait; -use crate::program::WitnessTrait; use super::UTXO; @@ -63,7 +65,7 @@ pub struct ProgramInput { /// The compiled program interface associated with the input. pub program: Box, /// The witness values required to satisfy the program. - pub witness: Box, + pub witness: Arc, } /// Represents an input designated for asset issuance or reissuance. @@ -168,8 +170,11 @@ impl PartialInput { impl ProgramInput { /// Creates a new `ProgramInput` from a `ProgramTrait` and its associated `WitnessTrait`. #[must_use] - pub fn new(program: Box, witness: Box) -> Self { - Self { program, witness } + pub fn new(program: Box, witness: impl Into) -> Self { + Self { + program, + witness: Arc::new(witness.into()), + } } } diff --git a/crates/simplex/src/lib.rs b/crates/simplex/src/lib.rs index a88873ec..009ec80e 100644 --- a/crates/simplex/src/lib.rs +++ b/crates/simplex/src/lib.rs @@ -8,6 +8,7 @@ pub use smplx_sdk::*; pub use smplx_test::config::TestConfig; pub use smplx_test::context::TestContext; +pub use smplx_test::mutantesting; pub use smplx_macros; pub use smplx_macros::{include_simf, test}; diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index 367f85ee..fff87bf8 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, WitnessTrait, RandomArguments, RandomWitness}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct ArrayTrStorageProgram { + program: Program, +} +impl ArrayTrStorageProgram { + pub const SOURCE: &'static str = derived_array_tr_storage::ARRAY_TR_STORAGE_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for ArrayTrStorageProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for ArrayTrStorageProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/array_tr_storage.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs index 5bb6d5a4..f5769d3a 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct Bytes32TrStorageProgram { + program: Program, +} +impl Bytes32TrStorageProgram { + pub const SOURCE: &'static str = derived_bytes32_tr_storage::BYTES32_TR_STORAGE_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for Bytes32TrStorageProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for Bytes32TrStorageProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index 96896c44..14776aca 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct DualCurrencyDepositProgram { + program: Program, +} +impl DualCurrencyDepositProgram { + pub const SOURCE: &'static str = derived_dual_currency_deposit::DUAL_CURRENCY_DEPOSIT_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for DualCurrencyDepositProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for DualCurrencyDepositProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/dual_currency_deposit.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index 95bbed3d..b7ceb91a 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct EitherWithSingleWitnessProgram { + program: Program, +} +impl EitherWithSingleWitnessProgram { + pub const SOURCE: &'static str = derived_either_with_single_witness::EITHER_WITH_SINGLE_WITNESS_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for EitherWithSingleWitnessProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for EitherWithSingleWitnessProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/either_with_single_witness.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index fd92a0a0..84da3693 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct ExoticValuesProgram { + program: Program, +} +impl ExoticValuesProgram { + pub const SOURCE: &'static str = derived_exotic_values::EXOTIC_VALUES_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for ExoticValuesProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for ExoticValuesProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/exotic_values.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index 08d24aa3..d3877ff6 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct ListCheckProgram { + program: Program, +} +impl ListCheckProgram { + pub const SOURCE: &'static str = derived_list_check::LIST_CHECK_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for ListCheckProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for ListCheckProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/list_check.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index 40107391..8487ff31 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct OptionOfferProgram { + program: Program, +} +impl OptionOfferProgram { + pub const SOURCE: &'static str = derived_option_offer::OPTION_OFFER_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for OptionOfferProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for OptionOfferProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/option_offer.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 9a733318..2ab28b63 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct OptionsProgram { + program: Program, +} +impl OptionsProgram { + pub const SOURCE: &'static str = derived_options::OPTIONS_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for OptionsProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for OptionsProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/options.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index 5d5fa88b..92e70b7f 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct SimpleStorageProgram { + program: Program, +} +impl SimpleStorageProgram { + pub const SOURCE: &'static str = derived_simple_storage::SIMPLE_STORAGE_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for SimpleStorageProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for SimpleStorageProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/simple_storage.simf"); fn main() -> Result<(), String> { diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index 73ddad0c..97973cae 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,6 +1,68 @@ use simplex::include_simf; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::program::Program; +use simplex::provider::SimplicityNetwork; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +#[derive(Clone)] +pub struct SingleBitProgram { + program: Program, +} +impl SingleBitProgram { + pub const SOURCE: &'static str = derived_single_bit::SINGLE_BIT_CONTRACT_SOURCE; + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments.into()), + } + } + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + #[must_use] + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} +impl AsRef for SingleBitProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} +impl AsMut for SingleBitProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + include_simf!("../../../../crates/simplex/tests/ui_simfs/single_bit.simf"); fn main() -> Result<(), String> { diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index f5f22969..2a86027e 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -23,3 +23,5 @@ toml = { workspace = true } syn = { version = "2.0.114", default-features = false, features = ["proc-macro", "full", "parsing", "derive", "clone-impls", "extra-traits", "printing"] } proc-macro2 = { version = "1.0.106", features = ["span-locations"] } quote = { version = "1.0.44" } +proptest = { version = "1.11.0" } + diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 2725aa8a..14109bfa 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -2,6 +2,8 @@ pub mod config; pub mod context; pub mod error; pub mod macros; +/// General utilities and traits for implementing property based testing in Simplicity contracts. +pub mod mutantesting; pub mod network_utils; pub use config::{RpcConfig, TEST_ENV_NAME, TestConfig}; diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs new file mode 100644 index 00000000..e270cec1 --- /dev/null +++ b/crates/test/src/mutantesting/core.rs @@ -0,0 +1,59 @@ +use crate::mutantesting::provider::MockProvider; +use proptest::prelude::BoxedStrategy; +use simplicityhl::elements::Script; +use simplicityhl::elements::pset::PartiallySignedTransaction; +use simplicityhl::{Arguments, WitnessValues}; +use smplx_sdk::program::ProgramError; +use smplx_sdk::program::core::SimplexProgram; +use smplx_sdk::provider::SimplicityNetwork; +use smplx_sdk::signer::{Signer, SignerError}; +use smplx_sdk::transaction::FinalTransaction; +use std::fmt::Debug; +use std::sync::Arc; + +pub struct FuzzContext { + pub signer: Option, + pub mock_provider: MockProvider, + pub network: SimplicityNetwork, +} + +use simplicityhl::simplicity::{RedeemNode, Value}; + +pub type ProgramExecResult = Result<(Arc, Value), ProgramError>; +pub trait FuzzableProgram: SimplexProgram { + fn build_program(args: impl Into, network: &SimplicityNetwork) -> (Box

, Script); +} + +pub trait FuzzableBaseContextGen { + fn build_base_transaction( + &self, + network: &SimplicityNetwork, + args: Arguments, + wit: WitnessValues, + ) -> FinalTransaction; +} + +pub trait FuzzableContextGen { + fn modify_transaction( + &self, + signer: &Option, + ft: FinalTransaction, + args: &Arguments, + wit: &WitnessValues, + ) -> Result; +} + +pub trait ArgGenFuzzStrategy: Debug { + fn get_strategy(&self, test_context: &FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues)>; +} + +pub trait ProgramCheck { + fn call( + &self, + ctx: &FuzzContext, + tx: &PartiallySignedTransaction, + arguments: &Arguments, + witness: &WitnessValues, + program_exec_result: ProgramExecResult, + ) -> Result<(), String>; +} diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs new file mode 100644 index 00000000..d13cd649 --- /dev/null +++ b/crates/test/src/mutantesting/engine.rs @@ -0,0 +1,137 @@ +use crate::mutantesting::core::{ + ArgGenFuzzStrategy, FuzzContext, FuzzableBaseContextGen, FuzzableContextGen, FuzzableProgram, ProgramCheck, + ProgramExecResult, +}; +use crate::mutantesting::provider::MockProvider; +use proptest::prelude::TestCaseError; +use simplicityhl::Arguments; +use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, WitnessTrait}; +use smplx_sdk::provider::SimplicityNetwork; +use smplx_sdk::signer::Signer; +use std::cell::RefCell; +use std::marker::PhantomData; + +pub struct SimplexFuzzEngineInner { + pub(crate) fuzz_context: FuzzContext, + pub(crate) strategy_storage: Vec>>, + pub(crate) base_gen: Option>>, + pub(crate) mod_gen: Option>>, +} + +pub struct SimplexFuzzEngine { + runner: RefCell, + inner: RefCell>, + _phantom: PhantomData, +} + +impl Default for FuzzContext { + fn default() -> Self { + let default_network = SimplicityNetwork::default_regtest(); + Self { + signer: None, + network: default_network, + mock_provider: MockProvider { + network: default_network, + }, + } + } +} + +impl FuzzContext { + fn with_signer(&mut self, signer: Signer) { + self.signer = Some(signer); + } +} + +impl SimplexFuzzEngine +where + Args: ArgumentsTrait + Into + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + std::fmt::Debug + Clone + 'static, + FuzzProgram: FuzzableProgram + Clone + 'static, +{ + pub fn from_config(mut config: proptest::test_runner::Config, _phantom: PhantomData) -> Self { + config.cases = 500; + Self { + runner: RefCell::new(proptest::test_runner::TestRunner::new(config)), + inner: RefCell::new(SimplexFuzzEngineInner { + fuzz_context: FuzzContext::default(), + strategy_storage: vec![], + base_gen: None, + mod_gen: None, + }), + _phantom, + } + } + + pub fn with_signer(&self, signer: Signer) { + self.inner.borrow_mut().fuzz_context.with_signer(signer); + } + + pub fn with_default_signer(&self) { + const DEFAULT_TEST_MNEMONIC: &str = "exist carry drive collect lend cereal occur much tiger just involve mean"; + + let network = self.inner.borrow().fuzz_context.network; + self.inner + .borrow_mut() + .fuzz_context + .with_signer(Signer::new(DEFAULT_TEST_MNEMONIC, Box::new(MockProvider { network }))); + } + + pub fn with_pset_base_gen_strategy(&self) + where + G: FuzzableBaseContextGen + Default + 'static, + { + self.inner.borrow_mut().base_gen = Some(Box::new(G::default())); + } + + pub fn with_pset_strategy(&self) + where + G: FuzzableContextGen + Default + 'static, + { + self.inner.borrow_mut().mod_gen = Some(Box::new(G::default())); + } + + pub fn with_arg_gen_strategy(&self) + where + S: ArgGenFuzzStrategy + Default + 'static, + { + self.inner.borrow_mut().strategy_storage.push(Box::new(S::default())); + } + + pub fn run_with_check(&self, program_check_fn: impl ProgramCheck) { + let mut runner = self.runner.borrow_mut(); + let inner = self.inner.borrow(); + + let base_gen = inner.base_gen.as_ref().expect("Base gen strategy must be configured"); + let modifier = inner.mod_gen.as_ref().expect("Mod gen strategy must be configured"); + + // TODO: remove strategies Vec, by now impossible to combine them, we can only use 1 + for strategy_gen in inner.strategy_storage.iter() { + let strategy = strategy_gen.get_strategy(&inner.fuzz_context); + match runner.run(&strategy, |(args, wit)| { + let context = &inner.fuzz_context; + let ft = base_gen.build_base_transaction(&context.network, args.clone(), wit.clone()); + // TODO: maybe make a couple of modification for one ft if non-default used? + let pst = modifier + .modify_transaction(&inner.fuzz_context.signer, ft, &args, &wit) + .unwrap(); + + let (failure_program, _script) = FuzzProgram::build_program(args.clone(), &context.network); + + // TODO: think how to provide a pset for a program execution environment + // let pst = PartiallySignedTransaction::from_tx(tx.clone()); + + let exec_result: ProgramExecResult = + failure_program.get_program().execute(&pst, &wit, 0, &context.network); + + match program_check_fn.call(context, &pst, &args, &wit, exec_result) { + Ok(_) => Ok(()), + Err(e) => Err(TestCaseError::fail(e)), + } + }) { + Ok(()) => (), + Err(e) => ::core::panic!("{}\n{}", e, runner), + }; + } + } +} diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs new file mode 100644 index 00000000..ca1e5510 --- /dev/null +++ b/crates/test/src/mutantesting/mod.rs @@ -0,0 +1,8 @@ +pub mod core; +pub mod engine; +pub mod provider; +pub mod strategy; + +pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; +pub use engine::SimplexFuzzEngine; +pub use proptest::test_runner::Config; diff --git a/crates/test/src/mutantesting/provider.rs b/crates/test/src/mutantesting/provider.rs new file mode 100644 index 00000000..9777a07f --- /dev/null +++ b/crates/test/src/mutantesting/provider.rs @@ -0,0 +1,64 @@ +use simplicityhl::elements::{Address, Script, Transaction, Txid}; +use smplx_sdk::provider::{ProviderError, ProviderTrait, SimplicityNetwork}; +use smplx_sdk::transaction::{TxReceipt, UTXO}; +use std::collections::HashMap; + +pub struct MockProvider { + pub network: SimplicityNetwork, +} + +impl MockProvider { + pub fn new(network: SimplicityNetwork) -> Self { + Self { network } + } +} + +impl ProviderTrait for MockProvider { + fn get_network(&self) -> &SimplicityNetwork { + &self.network + } + + fn broadcast_transaction(&self, _tx: &Transaction) -> Result, ProviderError> { + unimplemented!("No network access needed for tests") + } + + fn wait(&self, _txid: &Txid) -> Result<(), ProviderError> { + unimplemented!("No network access needed for tests") + } + + fn fetch_tip_height(&self) -> Result { + unimplemented!("No network access needed for tests") + } + + fn fetch_tip_block_hash(&self) -> Result { + unimplemented!("No network access needed for tests") + } + + fn fetch_tip_timestamp(&self) -> Result { + unimplemented!("No network access needed for tests") + } + + fn fetch_block_hash_at_height(&self, _block_height: u32) -> Result { + unimplemented!("No network access needed for tests") + } + + fn fetch_block_txids(&self, _block_hash: &str) -> Result, ProviderError> { + unimplemented!("No network access needed for tests") + } + + fn fetch_transaction(&self, _txid: &Txid) -> Result { + unimplemented!("No network access needed for tests") + } + + fn fetch_address_utxos(&self, _address: &Address) -> Result, ProviderError> { + unimplemented!("No network access needed for tests") + } + + fn fetch_scripthash_utxos(&self, _script: &Script) -> Result, ProviderError> { + unimplemented!("No network access needed for tests") + } + + fn fetch_fee_estimates(&self) -> Result, ProviderError> { + Ok(HashMap::new()) + } +} diff --git a/crates/test/src/mutantesting/strategy.rs b/crates/test/src/mutantesting/strategy.rs new file mode 100644 index 00000000..70370907 --- /dev/null +++ b/crates/test/src/mutantesting/strategy.rs @@ -0,0 +1,2 @@ +pub mod args; +pub mod pset; diff --git a/crates/test/src/mutantesting/strategy/args.rs b/crates/test/src/mutantesting/strategy/args.rs new file mode 100644 index 00000000..975be9e2 --- /dev/null +++ b/crates/test/src/mutantesting/strategy/args.rs @@ -0,0 +1,219 @@ +use proptest::prelude::{BoxedStrategy, Strategy}; +use proptest::strategy::{NewTree, ValueTree}; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; + +use crate::mutantesting::FuzzContext; +use crate::mutantesting::core::ArgGenFuzzStrategy; +use proptest::prelude::Rng; +use proptest::test_runner::{TestRng, TestRunner}; +use simplicityhl::str::WitnessName; +use simplicityhl::{Arguments, ResolvedType, Value, WitnessValues}; +use smplx_sdk::program::{RandomArguments, RandomWitness}; +use std::collections::HashMap; + +pub struct Random { + phantom_data: PhantomData<(Args, Wit)>, +} + +impl Default for Random { + fn default() -> Self { + Self { + phantom_data: PhantomData, + } + } +} + +impl Debug for Random { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Random trees...") + } +} + +pub struct RandomValueTree(T); + +impl ValueTree for RandomValueTree { + type Value = T; + fn current(&self) -> T { + self.0.clone() + } + fn simplify(&mut self) -> bool { + false + } + fn complicate(&mut self) -> bool { + false + } +} + +impl Strategy for Random { + type Tree = RandomValueTree<(Arguments, WitnessValues)>; + type Value = (Arguments, WitnessValues); + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + Ok(RandomValueTree(( + Args::generate_arguments(runner.rng()), + Wit::generate_witness(runner.rng()), + ))) + } +} + +impl + ArgGenFuzzStrategy for Random +{ + fn get_strategy(&self, _test_context: &FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues)> { + Random::::default().boxed() + } +} + +pub struct RandomValuePool { + phantom_data: PhantomData<(Args, Wit)>, + _value_pool: ValuePool, +} + +impl Default for RandomValuePool { + fn default() -> Self { + Self { + phantom_data: PhantomData, + _value_pool: ValuePool::default(), + } + } +} + +impl + ArgGenFuzzStrategy for RandomValuePool +{ + fn get_strategy(&self, _test_context: &FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues)> { + RandomValuePool:: { + phantom_data: Default::default(), + _value_pool: ValuePool::default(), + } + .boxed() + } +} + +impl Debug for RandomValuePool { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "RandomValuePool trees...") + } +} + +pub struct ValuePoolValueTree { + current: T, + value_pool: ValuePool, + rng: TestRng, + cnt: usize, + max_bound: usize, +} + +impl ValuePoolValueTree { + pub fn check_utilization(&self) -> bool { + self.cnt < self.max_bound + } +} + +impl ValueTree for ValuePoolValueTree<(Arguments, WitnessValues)> { + type Value = (Arguments, WitnessValues); + fn current(&self) -> Self::Value { + self.current.clone() + } + fn simplify(&mut self) -> bool { + let modified_witness = self + .value_pool + .probabilistically_replace(self.current.1.clone(), &mut self.rng); + self.current.1 = modified_witness; + self.cnt += 1; + self.check_utilization() + } + fn complicate(&mut self) -> bool { + self.simplify() + } +} + +impl Strategy + for RandomValuePool +{ + type Tree = ValuePoolValueTree<(Arguments, WitnessValues)>; + type Value = (Arguments, WitnessValues); + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + let args = Args::generate_arguments(runner.rng()); + let wit = Wit::generate_witness(runner.rng()); + let pool = ValuePool::new(&wit.clone(), &args.clone()); + let wit = pool.probabilistically_replace(wit, runner.rng()); + + Ok(ValuePoolValueTree { + current: (args, wit), + value_pool: pool, + rng: runner.rng().clone(), + cnt: 0, + max_bound: 50, + }) + } +} + +#[derive(Default)] +pub struct ValuePool { + pool: HashMap>, + witness_structure: HashMap, +} + +impl ValuePool { + pub fn new(wit: &WitnessValues, args: &Arguments) -> Self { + let mut pool: HashMap> = HashMap::new(); + let mut witness_structure: HashMap = HashMap::new(); + + for (name, val) in wit.iter() { + witness_structure.insert(name.clone(), val.ty().clone()); + pool.entry(val.ty().clone()) + .and_modify(|counter| counter.push(val.clone())) + .or_insert(vec![val.clone()]); + } + + for (_, val) in args.iter() { + pool.entry(val.ty().clone()) + .and_modify(|counter| counter.push(val.clone())) + .or_insert(vec![val.clone()]); + } + // TODO: add possibility in simplex to generate any kind of type + + Self { + pool, + witness_structure, + } + } + + pub fn sample(&self, ty: &ResolvedType, rng: &mut TestRng) -> Option { + self.pool.get(ty).and_then(|values| { + if values.is_empty() { + None + } else { + let idx = rng.random_range(0..values.len()); + Some(values[idx].clone()) + } + }) + } + + pub fn generate_witness(&self, rng: &mut TestRng) -> WitnessValues { + let mut map = HashMap::new(); + for (name, ty) in &self.witness_structure { + if let Some(val) = self.sample(ty, rng) { + map.insert(name.clone(), val); + } + } + WitnessValues::from(map) + } + + pub fn probabilistically_replace(&self, wit: WitnessValues, rng: &mut TestRng) -> WitnessValues { + let mut map = HashMap::new(); + for (name, val) in wit.iter() { + let should_replace: bool = rng.random(); + if should_replace { + let sampled = self.sample(val.ty(), rng).unwrap_or_else(|| val.clone()); + map.insert(name.clone(), sampled); + } else { + map.insert(name.clone(), val.clone()); + } + } + WitnessValues::from(map) + } +} diff --git a/crates/test/src/mutantesting/strategy/pset.rs b/crates/test/src/mutantesting/strategy/pset.rs new file mode 100644 index 00000000..1d78b0be --- /dev/null +++ b/crates/test/src/mutantesting/strategy/pset.rs @@ -0,0 +1,67 @@ +use crate::mutantesting::core::{FuzzableBaseContextGen, FuzzableContextGen, FuzzableProgram}; +use simplicityhl::elements::hashes::Hash; +use simplicityhl::elements::pset::PartiallySignedTransaction; +use simplicityhl::elements::{OutPoint, TxOut, Txid}; +use simplicityhl::{Arguments, WitnessValues}; +use smplx_sdk::provider::SimplicityNetwork; +use smplx_sdk::signer::{Signer, SignerError}; +use smplx_sdk::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; + +#[derive(Default)] +pub struct DefaultBaseContextGen {} + +impl> FuzzableBaseContextGen for DefaultBaseContextGen { + // TODO: move base transaction creation into Tree strategy + fn build_base_transaction( + &self, + network: &SimplicityNetwork, + args: Arguments, + wit: WitnessValues, + ) -> FinalTransaction { + const DEFAULT_FAUCET: u64 = 1 << 32; + + let mut ft = FinalTransaction::new(); + + let (failure_program, failure_script) = FuzzProgram::build_program(args, network); + + let txout = { + let mut r = TxOut::new_fee(DEFAULT_FAUCET, network.policy_asset()); + r.script_pubkey = failure_script; + r + }; + + ft.add_program_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::all_zeros(), 0), + txout, + secrets: None, + }), + ProgramInput::new(Box::new(failure_program.get_program().clone()), wit), + RequiredSignature::None, + ); + + ft + } +} + +#[derive(Default)] +pub struct DefaultContextGen {} + +impl> FuzzableContextGen for DefaultContextGen { + // todo: move into one strategy + fn modify_transaction( + &self, + _signer: &Option, + ft: FinalTransaction, + _args: &Arguments, + _wit: &WitnessValues, + ) -> Result { + Ok(ft.extract_pst().0) + // TODO: fix, incorrect witness_utxo extraction from sign_tx method + // idea - to sign and retrieve a valid finalized transaction to check, but not partial one without fees + // match signer { + // None => Ok(ft.extract_pst().0), + // Some(s) => Ok(s.finalize_offline(&ft)?.0), + // } + } +} diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 1e42366a..454dcd63 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -156,6 +156,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitcoin" version = "0.32.100" @@ -514,6 +529,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -876,6 +897,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.37.3" @@ -943,6 +973,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.0", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax 0.8.10", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "psm" version = "0.1.31" @@ -953,6 +1002,12 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.45" @@ -1033,6 +1088,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1149,6 +1213,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1416,6 +1492,7 @@ version = "0.0.7" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", "serde", "simplicityhl", @@ -1555,6 +1632,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1600,6 +1683,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/examples/basic/tests/basic_test.rs b/examples/basic/tests/basic_test.rs index 2c53f8e7..ed219376 100644 --- a/examples/basic/tests/basic_test.rs +++ b/examples/basic/tests/basic_test.rs @@ -43,7 +43,7 @@ fn spend_p2pk(context: &simplex::TestContext) -> anyhow::Result> { ft.add_program_input( PartialInput::new(p2pk_utxos[0].clone()), - ProgramInput::new(Box::new(p2pk.as_ref().clone()), Box::new(witness.clone())), + ProgramInput::new(Box::new(p2pk.as_ref().clone()), witness.clone()), RequiredSignature::Witness("SIGNATURE".to_string()), ); diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index a7aac83f..4c01a386 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -156,6 +156,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitcoin" version = "0.32.100" @@ -514,6 +529,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -876,6 +897,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.37.3" @@ -943,6 +973,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.13.0", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax 0.8.10", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "psm" version = "0.1.31" @@ -953,6 +1002,12 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.45" @@ -1033,6 +1088,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1149,6 +1213,18 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1416,6 +1492,7 @@ version = "0.0.7" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", "serde", "simplicityhl", @@ -1555,6 +1632,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1600,6 +1683,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/fixtures/simf/failure_test.simf b/fixtures/simf/failure_test.simf new file mode 100644 index 00000000..ea5e1861 --- /dev/null +++ b/fixtures/simf/failure_test.simf @@ -0,0 +1,13 @@ +fn not(bit: bool) -> bool { + ::into(jet::complement_1(::into(bit))) +} + +fn main() { + let failure_value: u256 = param::FAILURE_VALUE; + let cmp_value: u256 = witness::CMP_VALUE; + + assert!(not(jet::eq_256( + failure_value, + cmp_value + ))); +} \ No newline at end of file diff --git a/fixtures/tests/basic_test.rs b/fixtures/tests/basic_test.rs index bffd67f1..c5b0b521 100644 --- a/fixtures/tests/basic_test.rs +++ b/fixtures/tests/basic_test.rs @@ -43,7 +43,7 @@ fn spend_p2pk(context: &simplex::TestContext) -> anyhow::Result<()> { ft.add_program_input( PartialInput::new(p2pk_utxos[0].clone()), - ProgramInput::new(Box::new(p2pk.as_ref().clone()), Box::new(witness.clone())), + ProgramInput::new(Box::new(p2pk.as_ref().clone()), witness), RequiredSignature::Witness("SIGNATURE".to_string()), ); diff --git a/fixtures/tests/log_level.rs b/fixtures/tests/log_level.rs index 8c65197a..e680c71a 100644 --- a/fixtures/tests/log_level.rs +++ b/fixtures/tests/log_level.rs @@ -30,7 +30,7 @@ fn dummy_log_level(context: simplex::TestContext) -> anyhow::Result<()> { ft.add_program_input( PartialInput::new(utxos[0].clone()), - ProgramInput::new(Box::new(dummy.as_ref().clone()), Box::new(DummyPanicWitness::default())), + ProgramInput::new(Box::new(dummy.as_ref().clone()), DummyPanicWitness::default()), RequiredSignature::None, ); diff --git a/fixtures/tests/multidep_test.rs b/fixtures/tests/multidep_test.rs index 52b86684..56947db6 100644 --- a/fixtures/tests/multidep_test.rs +++ b/fixtures/tests/multidep_test.rs @@ -39,7 +39,7 @@ fn spend_p2pk(context: &simplex::TestContext) -> anyhow::Result<()> { ft.add_program_input( PartialInput::new(utxos[0].clone()), - ProgramInput::new(Box::new(program.as_ref().clone()), Box::new(witness.clone())), + ProgramInput::new(Box::new(program.as_ref().clone()), witness), RequiredSignature::None, ); diff --git a/fixtures/tests/nested_sig.rs b/fixtures/tests/nested_sig.rs index d3f6e0b8..fd2a208b 100644 --- a/fixtures/tests/nested_sig.rs +++ b/fixtures/tests/nested_sig.rs @@ -44,7 +44,7 @@ fn spend_nested_sig( ft.add_program_input( PartialInput::new(utxos[0].clone()), - ProgramInput::new(Box::new(program.as_ref().clone()), Box::new(witness)), + ProgramInput::new(Box::new(program.as_ref().clone()), witness), RequiredSignature::witness_with_path("INHERIT_OR_NOT", sig_path), ); diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs new file mode 100644 index 00000000..a3a9b97b --- /dev/null +++ b/fixtures/tests/prop_testing.rs @@ -0,0 +1,81 @@ +use simplex::mutantesting; +use simplex::mutantesting::strategy::args::{Random, RandomValuePool}; +use simplex::mutantesting::strategy::pset::{DefaultBaseContextGen, DefaultContextGen}; +use simplex::mutantesting::{FuzzContext, ProgramCheck, ProgramExecResult, SimplexFuzzEngine}; +use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex_fixtures::artifacts::failure_test::FailureTestProgram; +use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; +use std::marker::PhantomData; + +struct FailureTestCheck; + +impl ProgramCheck for FailureTestCheck { + fn call( + &self, + _ctx: &FuzzContext, + _tx: &PartiallySignedTransaction, + _arguments: &Arguments, + _witness: &WitnessValues, + program_exec_result: ProgramExecResult, + ) -> Result<(), String> { + // dbg!(&program_exec_result); + let args = FailureTestArguments::from_arguments(_arguments).unwrap(); + let witness = FailureTestWitness::from_witness(_witness).unwrap(); + if args.failure_value == witness.cmp_value { + return Err(format!("Failed contract, {program_exec_result:?}")); + } + if program_exec_result.is_err() { + println!("error: {program_exec_result:?}"); + return Err(format!("Failed contract, {program_exec_result:?}")); + } + Ok(()) + } +} + +mod tests { + use super::*; + #[ignore] + #[test] + fn possible_interface() -> anyhow::Result<()> { + let fuzz_engine = + SimplexFuzzEngine::::from_config( + mutantesting::Config::default(), + PhantomData, + ); + + // TODO: REMOVE signer, seems to be redundant due to sign_tx method signature (it elides witness_utxo) + fuzz_engine.with_default_signer(); + // TODO: move 2 lines into 1 strategy + fuzz_engine.with_pset_base_gen_strategy::(); + fuzz_engine.with_pset_strategy::(); + + fuzz_engine.with_arg_gen_strategy::>(); + + fuzz_engine.run_with_check(FailureTestCheck); + + Ok(()) + } + + #[ignore] + #[test] + fn possible_interface_2() -> anyhow::Result<()> { + let fuzz_engine = + SimplexFuzzEngine::::from_config( + mutantesting::Config::default(), + PhantomData, + ); + + // TODO: REMOVE signer, seems to be redundant due to sign_tx method signature (it elides witness_utxo) + fuzz_engine.with_default_signer(); + // TODO: move 2 lines into 1 strategy + fuzz_engine.with_pset_base_gen_strategy::(); + fuzz_engine.with_pset_strategy::(); + + fuzz_engine.with_arg_gen_strategy::>(); + + fuzz_engine.run_with_check(FailureTestCheck); + + Ok(()) + } +} From 60cd7bc4564e4a5540b7bdda61caa646517ac783 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 10 Jun 2026 14:53:28 +0300 Subject: [PATCH 04/21] smplx_test: add util for random type generation --- Cargo.lock | 1 + crates/test/Cargo.toml | 1 + crates/test/src/mutantesting/mod.rs | 1 + crates/test/src/mutantesting/utils.rs | 144 ++++++++++++++++++++++++++ examples/basic/Cargo.lock | 1 + fixtures/Cargo.lock | 1 + 6 files changed, 149 insertions(+) create mode 100644 crates/test/src/mutantesting/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 65908505..5e365693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1591,6 +1591,7 @@ dependencies = [ "proc-macro2", "proptest", "quote", + "rand 0.9.4", "serde", "simplicityhl", "smplx-regtest", diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 2a86027e..39eec76a 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -19,6 +19,7 @@ simplicityhl = { workspace = true } electrsd = { workspace = true } serde = { workspace = true } toml = { workspace = true } +rand = { workspace = true } syn = { version = "2.0.114", default-features = false, features = ["proc-macro", "full", "parsing", "derive", "clone-impls", "extra-traits", "printing"] } proc-macro2 = { version = "1.0.106", features = ["span-locations"] } diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index ca1e5510..e6a49d8c 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -2,6 +2,7 @@ pub mod core; pub mod engine; pub mod provider; pub mod strategy; +pub mod utils; pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; pub use engine::SimplexFuzzEngine; diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/mutantesting/utils.rs new file mode 100644 index 00000000..6af9a5ba --- /dev/null +++ b/crates/test/src/mutantesting/utils.rs @@ -0,0 +1,144 @@ +use rand::Rng; +use simplicityhl::num::{NonZeroPow2Usize, U256}; +use simplicityhl::types::{TypeInner, UIntType}; +use simplicityhl::value::ValueConstructible; +use simplicityhl::{ResolvedType, Value}; + +pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> Value { + match ty.as_inner() { + TypeInner::Either(left_ty, right_ty) => match rng.random::() { + true => Value::left(generate_value_by_ty(left_ty, rng), (**right_ty).clone()), + false => Value::right((**left_ty).clone(), generate_value_by_ty(right_ty, rng)), + }, + TypeInner::Option(option_ty) => match rng.random::() { + true => Value::some(generate_value_by_ty(option_ty, rng)), + false => Value::none((**option_ty).clone()), + }, + TypeInner::Boolean => Value::from(rng.random::()), + TypeInner::UInt(x) => match x { + UIntType::U1 => Value::u1(rng.random::() & 0x0001), + UIntType::U2 => Value::u2(rng.random::() & 0x0003), + UIntType::U4 => Value::u4(rng.random::() & 0x000F), + UIntType::U8 => Value::u8(rng.random::()), + UIntType::U16 => Value::u16(rng.random::()), + UIntType::U32 => Value::u32(rng.random::()), + UIntType::U64 => Value::u64(rng.random::()), + UIntType::U128 => Value::u128(rng.random::()), + UIntType::U256 => Value::u256(U256::from_byte_array(rng.random())), + }, + TypeInner::Tuple(tuple_ty) => Value::tuple(tuple_ty.iter().map(|x| generate_value_by_ty(x, rng))), + TypeInner::Array(array_ty, size) => Value::array( + (0..*size).map(|_| generate_value_by_ty(array_ty, rng)), + (**array_ty).clone(), + ), + TypeInner::List(list_ty, size_pow_2) => { + let size = rng.random_range(0..size_pow_2.get()); + Value::list( + (0..size).map(|_| generate_value_by_ty(list_ty, rng)), + (**list_ty).clone(), + *size_pow_2, + ) + } + _ => Value::unit(), + } +} + +pub fn generate_value_by_ty_iterative(root_ty: &ResolvedType, rng: &mut R) -> Value { + enum Step<'a> { + Gen(&'a ResolvedType), + AssembleEither(bool, ResolvedType), + AssembleOption, + AssembleTuple(usize), + AssembleArray(usize, ResolvedType), + AssembleList(usize, ResolvedType, NonZeroPow2Usize), + } + + let mut tasks = vec![Step::Gen(root_ty)]; + let mut results: Vec = vec![]; + + while let Some(task) = tasks.pop() { + match task { + Step::Gen(ty) => match ty.as_inner() { + TypeInner::Boolean => results.push(Value::from(rng.random::())), + TypeInner::UInt(x) => { + let val = match x { + UIntType::U1 => Value::u1(rng.random::() & 0x01), + UIntType::U2 => Value::u2(rng.random::() & 0x03), + UIntType::U4 => Value::u4(rng.random::() & 0x0F), + UIntType::U8 => Value::u8(rng.random::()), + UIntType::U16 => Value::u16(rng.random::()), + UIntType::U32 => Value::u32(rng.random::()), + UIntType::U64 => Value::u64(rng.random::()), + UIntType::U128 => Value::u128(rng.random::()), + UIntType::U256 => Value::u256(U256::from_byte_array(rng.random())), + }; + results.push(val); + } + TypeInner::Either(left_ty, right_ty) => { + if rng.random::() { + tasks.push(Step::AssembleEither(true, (**right_ty).clone())); + tasks.push(Step::Gen(left_ty)); + } else { + tasks.push(Step::AssembleEither(false, (**left_ty).clone())); + tasks.push(Step::Gen(right_ty)); + } + } + TypeInner::Option(inner_ty) => { + if rng.random::() { + tasks.push(Step::AssembleOption); + tasks.push(Step::Gen(inner_ty)); + } else { + results.push(Value::none((**inner_ty).clone())); + } + } + TypeInner::Tuple(types) => { + tasks.push(Step::AssembleTuple(types.len())); + for t in types.iter().rev() { + tasks.push(Step::Gen(t)); + } + } + TypeInner::Array(inner_ty, size) => { + tasks.push(Step::AssembleArray(*size, (**inner_ty).clone())); + for _ in 0..*size { + tasks.push(Step::Gen(inner_ty)); + } + } + TypeInner::List(inner_ty, max_pow2) => { + let len = rng.random_range(0..max_pow2.get()); + tasks.push(Step::AssembleList(len, (**inner_ty).clone(), *max_pow2)); + for _ in 0..len { + tasks.push(Step::Gen(inner_ty)); + } + } + _ => results.push(Value::unit()), + }, + + Step::AssembleEither(is_left, other_ty) => { + let val = results.pop().unwrap(); + results.push(if is_left { + Value::left(val, other_ty) + } else { + Value::right(other_ty, val) + }); + } + Step::AssembleOption => { + let val = results.pop().unwrap(); + results.push(Value::some(val)); + } + Step::AssembleTuple(len) => { + let items: Vec<_> = results.drain(results.len() - len..).collect(); + results.push(Value::tuple(items)); + } + Step::AssembleArray(size, ty) => { + let items: Vec<_> = results.drain(results.len() - size..).collect(); + results.push(Value::array(items, ty)); + } + Step::AssembleList(len, ty, max) => { + let items: Vec<_> = results.drain(results.len() - len..).collect(); + results.push(Value::list(items, ty, max)); + } + } + } + + results.pop().expect("Stack underflow") +} diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 454dcd63..2de09292 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -1494,6 +1494,7 @@ dependencies = [ "proc-macro2", "proptest", "quote", + "rand 0.9.4", "serde", "simplicityhl", "smplx-regtest", diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index 4c01a386..fa4d1e62 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -1494,6 +1494,7 @@ dependencies = [ "proc-macro2", "proptest", "quote", + "rand 0.9.4", "serde", "simplicityhl", "smplx-regtest", From 9e5655e420f11a496ac3120ca352a921f755ffc0 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 10 Jun 2026 15:43:57 +0300 Subject: [PATCH 05/21] simplex: update ui tests with random value generation --- crates/simplex/tests/ui/array_tr_storage.rs | 108 ++++++++++++--- crates/simplex/tests/ui/bytes32_tr_storage.rs | 97 +++++++++++--- .../simplex/tests/ui/dual_currency_deposit.rs | 110 +++++++++++---- .../tests/ui/either_with_single_witness.rs | 126 +++++++++++++----- crates/simplex/tests/ui/exotic_values.rs | 100 +++++++++++--- crates/simplex/tests/ui/list_check.rs | 83 +++++++++++- crates/simplex/tests/ui/option_offer.rs | 106 ++++++++++++--- crates/simplex/tests/ui/options.rs | 100 +++++++++++--- crates/simplex/tests/ui/simple_storage.rs | 106 ++++++++++++--- crates/simplex/tests/ui/single_bit.rs | 103 +++++++++++--- crates/test/src/mutantesting/mod.rs | 4 +- 11 files changed, 848 insertions(+), 195 deletions(-) diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index fff87bf8..5eaa0532 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, WitnessTrait, RandomArguments, RandomWitness}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct ArrayTrStorageProgram { program: Program, @@ -66,9 +69,10 @@ impl AsMut for ArrayTrStorageProgram { include_simf!("../../../../crates/simplex/tests/ui_simfs/array_tr_storage.simf"); fn main() -> Result<(), String> { - test_e2e_behaviour()?; - test_default()?; - test_e2e_random_behaviour()?; + let _ = test_e2e_behaviour()?; + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -101,39 +105,101 @@ fn test_default() -> Result<(), String> { Ok(()) } -fn test_e2e_random_behaviour() -> Result<(), String>{ +fn test_e2e_random_behaviour() -> Result<(), String> { for seed in 0..32 { use simplex::rand::{rngs::StdRng, SeedableRng}; let mut rng = StdRng::seed_from_u64(seed); - let original_witness = - derived_array_tr_storage::ArrayTrStorageWitness::generate_witness_raw(&mut rng); + let original_witness = derived_array_tr_storage::ArrayTrStorageWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; + let recovered_witness = derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_array_tr_storage::ArrayTrStorageWitness::generate_witness(&mut rng); + let rand_raw_witness_values = derived_array_tr_storage::ArrayTrStorageWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = - derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments_raw(&mut rng); + let original_arguments = derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); - }; + } Ok(()) -} \ No newline at end of file +} + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_array_tr_storage::ArrayTrStorageWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_array_tr_storage::ArrayTrStorageArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs index f5769d3a..c85cb230 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct Bytes32TrStorageProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -77,15 +81,13 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; + let recovered_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_bytes32_tr_storage::Bytes32TrStorageArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -109,17 +111,14 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let mut rng = StdRng::seed_from_u64(seed); - let original_witness = - derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness_raw(&mut rng); + let original_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; + let recovered_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness(&mut rng); + let rand_raw_witness_values = derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); @@ -138,3 +137,71 @@ fn test_e2e_random_behaviour() -> Result<(), String> { } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_bytes32_tr_storage::Bytes32TrStorageWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_bytes32_tr_storage::Bytes32TrStorageArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} \ No newline at end of file diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index 14776aca..af1f5753 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct DualCurrencyDepositProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/dual_currency_deposit.s fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -77,17 +81,14 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_dual_currency_deposit::DualCurrencyDepositWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; + let recovered_witness = derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_dual_currency_deposit::DualCurrencyDepositArguments::default(); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments( - &arguments_values, - )?; + derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -112,15 +113,11 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let mut rng = StdRng::seed_from_u64(seed); let original_witness = - derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness_raw( - &mut rng, - ); + derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); let recovered_witness = - derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness( - &witness_values, - )?; + derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); @@ -130,23 +127,86 @@ fn test_e2e_random_behaviour() -> Result<(), String> { rng = StdRng::seed_from_u64(seed); let original_arguments = - derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments_raw( - &mut rng, - ); + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments( - &arguments_values, - )?; + derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = - derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments( - &mut rng, - ); + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_dual_currency_deposit::DualCurrencyDepositWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = + derived_dual_currency_deposit::DualCurrencyDepositArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index b7ceb91a..6bdc44fd 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct EitherWithSingleWitnessProgram { program: Program, @@ -67,31 +70,26 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/either_with_single_witn fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } fn test_e2e_behaviour() -> Result<(), String> { - let original_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::default(); + let original_witness = derived_either_with_single_witness::EitherWithSingleWitnessWitness::default(); let witness_values = original_witness.build_witness(); let recovered_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness( - &witness_values, - )?; + derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); - let original_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::default(); + let original_arguments = derived_either_with_single_witness::EitherWithSingleWitnessArguments::default(); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( - &arguments_values, - )?; + derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -116,43 +114,105 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let mut rng = StdRng::seed_from_u64(seed); let original_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness_raw( - &mut rng, - ); + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); let recovered_witness = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness( - &witness_values, - )?; + derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = - derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness( - &mut rng, - ); + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); let original_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments_raw( - &mut rng, - ); + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); let recovered_arguments = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( - &arguments_values, - )?; + derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); let rand_raw_witness_values = - derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments( - &mut rng, - ); + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = + derived_either_with_single_witness::EitherWithSingleWitnessWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = + derived_either_with_single_witness::EitherWithSingleWitnessArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( + &arguments_values, + )?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( + &arguments_values, + )?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index 84da3693..86473142 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct ExoticValuesProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/exotic_values.simf"); fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -83,8 +87,7 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_arguments = derived_exotic_values::ExoticValuesArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -111,28 +114,91 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let original_witness = derived_exotic_values::ExoticValuesWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; + let recovered_witness = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_exotic_values::ExoticValuesWitness::generate_witness(&mut rng); + let rand_raw_witness_values = derived_exotic_values::ExoticValuesWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = - derived_exotic_values::ExoticValuesArguments::generate_arguments_raw(&mut rng); + let original_arguments = derived_exotic_values::ExoticValuesArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_exotic_values::ExoticValuesArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = derived_exotic_values::ExoticValuesArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_exotic_values::ExoticValuesWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_exotic_values::ExoticValuesArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index d3877ff6..ee53836b 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct ListCheckProgram { program: Program, @@ -68,8 +71,10 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/list_check.simf"); fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; let _ = test_build_panic()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -166,3 +171,71 @@ fn test_e2e_random_behaviour() -> Result<(), String> { Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_list_check::ListCheckWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_list_check::ListCheckWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_list_check::ListCheckWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_list_check::ListCheckArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_list_check::ListCheckArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_list_check::ListCheckArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index 8487ff31..b438d57f 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct OptionOfferProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/option_offer.simf"); fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -77,15 +81,13 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_option_offer::OptionOfferWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + let recovered_witness = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_option_offer::OptionOfferArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -109,32 +111,94 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let mut rng = StdRng::seed_from_u64(seed); - let original_witness = - derived_option_offer::OptionOfferWitness::generate_witness_raw(&mut rng); + let original_witness = derived_option_offer::OptionOfferWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + let recovered_witness = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_option_offer::OptionOfferWitness::generate_witness(&mut rng); + let rand_raw_witness_values = derived_option_offer::OptionOfferWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = - derived_option_offer::OptionOfferArguments::generate_arguments_raw(&mut rng); + let original_arguments = derived_option_offer::OptionOfferArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_option_offer::OptionOfferArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = derived_option_offer::OptionOfferArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_option_offer::OptionOfferWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_option_offer::OptionOfferArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 2ab28b63..7df3a988 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct OptionsProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/options.simf"); fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -77,15 +81,13 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_options::OptionsWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_options::OptionsWitness::from_witness(&witness_values)?; + let recovered_witness = derived_options::OptionsWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_options::OptionsArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_options::OptionsArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_options::OptionsArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -112,8 +114,7 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let original_witness = derived_options::OptionsWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_options::OptionsWitness::from_witness(&witness_values)?; + let recovered_witness = derived_options::OptionsWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); @@ -121,18 +122,83 @@ fn test_e2e_random_behaviour() -> Result<(), String> { assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = - derived_options::OptionsArguments::generate_arguments_raw(&mut rng); + let original_arguments = derived_options::OptionsArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_options::OptionsArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_options::OptionsArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_options::OptionsArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = derived_options::OptionsArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_options::OptionsWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_options::OptionsWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_options::OptionsWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_options::OptionsArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_options::OptionsArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_options::OptionsArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index 92e70b7f..6abc92ac 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct SimpleStorageProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/simple_storage.simf"); fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -77,15 +81,13 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_simple_storage::SimpleStorageWitness::default(); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + let recovered_witness = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_simple_storage::SimpleStorageArguments::default(); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); Ok(()) @@ -109,32 +111,94 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let mut rng = StdRng::seed_from_u64(seed); - let original_witness = - derived_simple_storage::SimpleStorageWitness::generate_witness_raw(&mut rng); + let original_witness = derived_simple_storage::SimpleStorageWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + let recovered_witness = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_simple_storage::SimpleStorageWitness::generate_witness(&mut rng); + let rand_raw_witness_values = derived_simple_storage::SimpleStorageWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = - derived_simple_storage::SimpleStorageArguments::generate_arguments_raw(&mut rng); + let original_arguments = derived_simple_storage::SimpleStorageArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_simple_storage::SimpleStorageArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = derived_simple_storage::SimpleStorageArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_simple_storage::SimpleStorageWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_simple_storage::SimpleStorageArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index 97973cae..3a2a1909 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,10 +1,13 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; - +use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; -use simplex::simplicityhl::elements::Script; use simplex::simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +use simplex::simplicityhl::elements::Script; +use simplex::simplicityhl::{Arguments, WitnessValues}; +use simplex::rand::RngCore; + #[derive(Clone)] pub struct SingleBitProgram { program: Program, @@ -67,8 +70,9 @@ include_simf!("../../../../crates/simplex/tests/ui_simfs/single_bit.simf"); fn main() -> Result<(), String> { let _ = test_e2e_behaviour()?; - let _ = test_default(); - let _ = test_e2e_random_behaviour(); + let _ = test_default()?; + let _ = test_e2e_random_behaviour()?; + let _ = test_e2e_random_value_generation_behaviour()?; Ok(()) } @@ -78,15 +82,13 @@ fn test_e2e_behaviour() -> Result<(), String> { let original_witness = derived_single_bit::SingleBitWitness { bit }; let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); let original_arguments = derived_single_bit::SingleBitArguments { flag }; let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); } @@ -114,28 +116,91 @@ fn test_e2e_random_behaviour() -> Result<(), String> { let original_witness = derived_single_bit::SingleBitWitness::generate_witness_raw(&mut rng); let witness_values = original_witness.build_witness(); - let recovered_witness = - derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + let recovered_witness = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; assert_eq!(original_witness, recovered_witness); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_single_bit::SingleBitWitness::generate_witness(&mut rng); + let rand_raw_witness_values = derived_single_bit::SingleBitWitness::generate_witness(&mut rng); assert_eq!(witness_values, rand_raw_witness_values); rng = StdRng::seed_from_u64(seed); - let original_arguments = - derived_single_bit::SingleBitArguments::generate_arguments_raw(&mut rng); + let original_arguments = derived_single_bit::SingleBitArguments::generate_arguments_raw(&mut rng); let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = - derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; assert_eq!(original_arguments, recovered_arguments); rng = StdRng::seed_from_u64(seed); - let rand_raw_witness_values = - derived_single_bit::SingleBitArguments::generate_arguments(&mut rng); + let rand_raw_witness_values = derived_single_bit::SingleBitArguments::generate_arguments(&mut rng); assert_eq!(arguments_values, rand_raw_witness_values); } Ok(()) } + +fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; + + let mut rng = StdRng::seed_from_u64(seed); + + { + let witness_values = derived_single_bit::SingleBitWitness::generate_witness(&mut rng); + + let witness_values = regenerate_witness_values(&witness_values, &mut rng); + let _ = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + + let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); + let _ = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; + } + + { + let arguments_values = derived_single_bit::SingleBitArguments::generate_arguments(&mut rng); + + let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); + let _ = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + + let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); + let _ = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + } + } + Ok(()) +} + +fn regenerate_witness_values(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); + } + Arguments::from(map) +} + +fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in wit.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + WitnessValues::from(map) +} + +fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { + use std::collections::HashMap; + + let mut map: HashMap<_,_> = Default::default(); + for (name, val) in args.iter() { + map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); + } + Arguments::from(map) +} \ No newline at end of file diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index e6a49d8c..28b15087 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -4,6 +4,8 @@ pub mod provider; pub mod strategy; pub mod utils; +pub use proptest::test_runner::Config; + pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; pub use engine::SimplexFuzzEngine; -pub use proptest::test_runner::Config; +pub use utils::{generate_value_by_ty, generate_value_by_ty_iterative}; From 391806481ad181038b23690af1130cd2ccf0b6ed Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 16 Jun 2026 14:30:55 +0300 Subject: [PATCH 06/21] ui tests: remove iterative generation for custom value in mutation testing --- crates/simplex/tests/ui/array_tr_storage.rs | 30 +---- crates/simplex/tests/ui/bytes32_tr_storage.rs | 28 +---- .../simplex/tests/ui/dual_currency_deposit.rs | 28 +---- .../tests/ui/either_with_single_witness.rs | 30 +---- crates/simplex/tests/ui/exotic_values.rs | 28 +---- crates/simplex/tests/ui/list_check.rs | 28 +---- crates/simplex/tests/ui/option_offer.rs | 28 +---- crates/simplex/tests/ui/options.rs | 28 +---- crates/simplex/tests/ui/simple_storage.rs | 28 +---- crates/simplex/tests/ui/single_bit.rs | 28 +---- crates/test/src/mutantesting/mod.rs | 2 +- crates/test/src/mutantesting/utils.rs | 102 +---------------- fixtures/simf/simple_storage.simf | 105 ++++++++++++++++++ 13 files changed, 118 insertions(+), 375 deletions(-) create mode 100644 fixtures/simf/simple_storage.simf diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index 5eaa0532..82583ba1 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -147,9 +147,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_array_tr_storage::ArrayTrStorageWitness::from_witness(&witness_values)?; } { @@ -157,9 +154,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_array_tr_storage::ArrayTrStorageArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -182,24 +176,4 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum map.insert(name.clone(), generate_value_by_ty(val.ty(), rng)); } Arguments::from(map) -} - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} +} \ No newline at end of file diff --git a/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs index c85cb230..39ed805b 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -149,9 +149,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_bytes32_tr_storage::Bytes32TrStorageWitness::from_witness(&witness_values)?; } { @@ -159,9 +156,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_bytes32_tr_storage::Bytes32TrStorageArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -185,23 +179,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} \ No newline at end of file diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index af1f5753..b45c41ff 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -153,9 +153,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_dual_currency_deposit::DualCurrencyDepositWitness::from_witness(&witness_values)?; } { @@ -164,9 +161,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_dual_currency_deposit::DualCurrencyDepositArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -190,23 +184,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index 6bdc44fd..2675db6a 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -155,9 +155,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_either_with_single_witness::EitherWithSingleWitnessWitness::from_witness(&witness_values)?; } { @@ -168,11 +165,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let _ = derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( &arguments_values, )?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_either_with_single_witness::EitherWithSingleWitnessArguments::from_arguments( - &arguments_values, - )?; } } Ok(()) @@ -196,23 +188,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index 86473142..a8e29767 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -146,9 +146,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_exotic_values::ExoticValuesWitness::from_witness(&witness_values)?; } { @@ -156,9 +153,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_exotic_values::ExoticValuesArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -182,23 +176,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index ee53836b..59f7bcfa 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -183,9 +183,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_list_check::ListCheckWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_list_check::ListCheckWitness::from_witness(&witness_values)?; } { @@ -193,9 +190,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_list_check::ListCheckArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_list_check::ListCheckArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -219,23 +213,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index b438d57f..997926a5 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -146,9 +146,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_option_offer::OptionOfferWitness::from_witness(&witness_values)?; } { @@ -156,9 +153,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_option_offer::OptionOfferArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -182,23 +176,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 7df3a988..2b2a03c4 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -146,9 +146,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_options::OptionsWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_options::OptionsWitness::from_witness(&witness_values)?; } { @@ -156,9 +153,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_options::OptionsArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_options::OptionsArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -182,23 +176,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index 6abc92ac..298b2ba2 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -146,9 +146,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_simple_storage::SimpleStorageWitness::from_witness(&witness_values)?; } { @@ -156,9 +153,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_simple_storage::SimpleStorageArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -182,23 +176,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index 3a2a1909..091c5d87 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty, generate_value_by_ty_iterative}; +use simplex::mutantesting::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; @@ -148,9 +148,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let witness_values = regenerate_witness_values(&witness_values, &mut rng); let _ = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; - - let witness_values = regenerate_witness_values_iterative(&witness_values, &mut rng); - let _ = derived_single_bit::SingleBitWitness::from_witness(&witness_values)?; } { @@ -158,9 +155,6 @@ fn test_e2e_random_value_generation_behaviour() -> Result<(), String> { let arguments_values = regenerate_arguments_values(&arguments_values, &mut rng); let _ = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; - - let arguments_values = regenerate_arguments_values_iterative(&arguments_values, &mut rng); - let _ = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; } } Ok(()) @@ -184,23 +178,3 @@ fn regenerate_arguments_values(args: &Arguments, rng: &mut dyn RngCore) -> Argum } Arguments::from(map) } - -fn regenerate_witness_values_iterative(wit: &WitnessValues, rng: &mut dyn RngCore) -> WitnessValues { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in wit.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - WitnessValues::from(map) -} - -fn regenerate_arguments_values_iterative(args: &Arguments, rng: &mut dyn RngCore) -> Arguments { - use std::collections::HashMap; - - let mut map: HashMap<_,_> = Default::default(); - for (name, val) in args.iter() { - map.insert(name.clone(), generate_value_by_ty_iterative(val.ty(), rng)); - } - Arguments::from(map) -} \ No newline at end of file diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index 28b15087..8cfc336e 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -8,4 +8,4 @@ pub use proptest::test_runner::Config; pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; pub use engine::SimplexFuzzEngine; -pub use utils::{generate_value_by_ty, generate_value_by_ty_iterative}; +pub use utils::generate_value_by_ty; diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/mutantesting/utils.rs index 6af9a5ba..612bcb2a 100644 --- a/crates/test/src/mutantesting/utils.rs +++ b/crates/test/src/mutantesting/utils.rs @@ -1,5 +1,5 @@ use rand::Rng; -use simplicityhl::num::{NonZeroPow2Usize, U256}; +use simplicityhl::num::U256; use simplicityhl::types::{TypeInner, UIntType}; use simplicityhl::value::ValueConstructible; use simplicityhl::{ResolvedType, Value}; @@ -42,103 +42,3 @@ pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> _ => Value::unit(), } } - -pub fn generate_value_by_ty_iterative(root_ty: &ResolvedType, rng: &mut R) -> Value { - enum Step<'a> { - Gen(&'a ResolvedType), - AssembleEither(bool, ResolvedType), - AssembleOption, - AssembleTuple(usize), - AssembleArray(usize, ResolvedType), - AssembleList(usize, ResolvedType, NonZeroPow2Usize), - } - - let mut tasks = vec![Step::Gen(root_ty)]; - let mut results: Vec = vec![]; - - while let Some(task) = tasks.pop() { - match task { - Step::Gen(ty) => match ty.as_inner() { - TypeInner::Boolean => results.push(Value::from(rng.random::())), - TypeInner::UInt(x) => { - let val = match x { - UIntType::U1 => Value::u1(rng.random::() & 0x01), - UIntType::U2 => Value::u2(rng.random::() & 0x03), - UIntType::U4 => Value::u4(rng.random::() & 0x0F), - UIntType::U8 => Value::u8(rng.random::()), - UIntType::U16 => Value::u16(rng.random::()), - UIntType::U32 => Value::u32(rng.random::()), - UIntType::U64 => Value::u64(rng.random::()), - UIntType::U128 => Value::u128(rng.random::()), - UIntType::U256 => Value::u256(U256::from_byte_array(rng.random())), - }; - results.push(val); - } - TypeInner::Either(left_ty, right_ty) => { - if rng.random::() { - tasks.push(Step::AssembleEither(true, (**right_ty).clone())); - tasks.push(Step::Gen(left_ty)); - } else { - tasks.push(Step::AssembleEither(false, (**left_ty).clone())); - tasks.push(Step::Gen(right_ty)); - } - } - TypeInner::Option(inner_ty) => { - if rng.random::() { - tasks.push(Step::AssembleOption); - tasks.push(Step::Gen(inner_ty)); - } else { - results.push(Value::none((**inner_ty).clone())); - } - } - TypeInner::Tuple(types) => { - tasks.push(Step::AssembleTuple(types.len())); - for t in types.iter().rev() { - tasks.push(Step::Gen(t)); - } - } - TypeInner::Array(inner_ty, size) => { - tasks.push(Step::AssembleArray(*size, (**inner_ty).clone())); - for _ in 0..*size { - tasks.push(Step::Gen(inner_ty)); - } - } - TypeInner::List(inner_ty, max_pow2) => { - let len = rng.random_range(0..max_pow2.get()); - tasks.push(Step::AssembleList(len, (**inner_ty).clone(), *max_pow2)); - for _ in 0..len { - tasks.push(Step::Gen(inner_ty)); - } - } - _ => results.push(Value::unit()), - }, - - Step::AssembleEither(is_left, other_ty) => { - let val = results.pop().unwrap(); - results.push(if is_left { - Value::left(val, other_ty) - } else { - Value::right(other_ty, val) - }); - } - Step::AssembleOption => { - let val = results.pop().unwrap(); - results.push(Value::some(val)); - } - Step::AssembleTuple(len) => { - let items: Vec<_> = results.drain(results.len() - len..).collect(); - results.push(Value::tuple(items)); - } - Step::AssembleArray(size, ty) => { - let items: Vec<_> = results.drain(results.len() - size..).collect(); - results.push(Value::array(items, ty)); - } - Step::AssembleList(len, ty, max) => { - let items: Vec<_> = results.drain(results.len() - len..).collect(); - results.push(Value::list(items, ty, max)); - } - } - } - - results.pop().expect("Stack underflow") -} diff --git a/fixtures/simf/simple_storage.simf b/fixtures/simf/simple_storage.simf new file mode 100644 index 00000000..cf2cbb35 --- /dev/null +++ b/fixtures/simf/simple_storage.simf @@ -0,0 +1,105 @@ +/* + * Simple Storage Program for Liquid + * + * Only the owner of the storage can modify the value. + * + * ==== IMPORTANT ==== + * + * Based on the following resources: + * https://github.com/ElementsProject/elements/blob/master/src/consensus/amount.h + * https://github.com/ElementsProject/rust-elements/blob/f6ffc7800df14b81c0f5ae1c94368a78b99612b9/src/blind.rs#L471 + * + * The maximum allowed amount is 2,100,000,000,000,000 + * (i.e., 21,000,000 × 10^8), which is approximately 51 bits. + */ + +fn checksig(pk: Pubkey, sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((pk, msg), sig); +} + +fn ensure_current_index_eq(expected_index: u32){ + assert!(jet::eq_32(jet::current_index(), expected_index)); +} + +fn ensure_input_and_output_script_hash_eq(index: u32) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), unwrap(jet::output_script_hash(index)))); +} + +fn ensure_output_is_op_return(index: u32) { + match jet::output_null_datum(index, 0) { + Some(entry: Option>>) => (), + None => panic!(), + } +} + +fn get_output_explicit_asset_amount(index: u32) -> (u256, u64) { + let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index)); + let (asset, amount): (Asset1, Amount1) = pair; + + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + +fn get_input_explicit_asset_amount(index: u32) -> (u256, u64) { + let pair: (Asset1, Amount1) = unwrap(jet::input_amount(index)); + let (asset, amount): (Asset1, Amount1) = pair; + + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = dbg!(get_output_explicit_asset_amount(index)); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_one_bit(bit: bool) { assert!(jet::eq_1(::into(bit), 1)); } +fn ensure_zero_bit(bit: bool) { assert!(jet::eq_1(::into(bit), 0)); } + +fn increment_by(index: u32, amount: u32) -> u32 { + let (carry, result): (bool, u32) = dbg!(jet::add_32(index, amount)); + ensure_zero_bit(carry); + result +} + +fn enforce_stage_checks(index: u32, new_value: u64) { + ensure_input_and_output_script_hash_eq(index); + + let (asset_bits, old_value): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(asset_bits, param::SLOT_ID)); + + ensure_output_asset_with_amount_eq(index, param::SLOT_ID, new_value); + + match dbg!(jet::lt_64(new_value, old_value)) { + // burn + true => { + let burn_output_index: u32 = increment_by(index, 1); + + let (carry, amount_to_burn): (bool, u64) = jet::subtract_64(old_value, new_value); + ensure_zero_bit(carry); + + ensure_output_is_op_return(burn_output_index); + ensure_output_asset_with_amount_eq(burn_output_index, param::SLOT_ID, amount_to_burn); + }, + // mint + false => { + let reissuance_output_index: u32 = increment_by(index, 1); + ensure_input_and_output_script_hash_eq(reissuance_output_index); + }, + }; +} + +fn main() { + let index: u32 = 0; + let n1: u32 = dbg!(111); + enforce_stage_checks(index, witness::NEW_VALUE); + + let n2: u32 = dbg!(222); + checksig(param::USER, witness::USER_SIGNATURE); + let n3: u32 = dbg!(333); +} From 0541a17cf757c9e425fcb0cb2f5f0a7a6362454a Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 16 Jun 2026 17:02:17 +0300 Subject: [PATCH 07/21] proptests: come up with another interface and implement it * add one more contract for testing * adapt previous one with new way of testing --- crates/sdk/src/program/core.rs | 39 ++ crates/sdk/src/signer/core.rs | 13 +- .../sdk/src/transaction/final_transaction.rs | 4 +- crates/sdk/src/transaction/partial_input.rs | 7 + crates/test/src/mutantesting/core.rs | 32 +- crates/test/src/mutantesting/engine.rs | 90 ++--- crates/test/src/mutantesting/mod.rs | 4 +- crates/test/src/mutantesting/strategy.rs | 1 - crates/test/src/mutantesting/strategy/args.rs | 24 +- crates/test/src/mutantesting/strategy/pset.rs | 67 ---- crates/test/src/mutantesting/utils.rs | 14 + fixtures/tests/prop_testing.rs | 371 +++++++++++++++--- 12 files changed, 433 insertions(+), 233 deletions(-) delete mode 100644 crates/test/src/mutantesting/strategy/pset.rs diff --git a/crates/sdk/src/program/core.rs b/crates/sdk/src/program/core.rs index ea0d965d..53f79998 100644 --- a/crates/sdk/src/program/core.rs +++ b/crates/sdk/src/program/core.rs @@ -218,6 +218,45 @@ impl ProgramTrait for Program { } } +impl ProgramTrait for &T { + fn get_argument_types(&self) -> Result { + (*self).get_argument_types() + } + + fn get_witness_types(&self) -> Result { + (*self).get_witness_types() + } + + fn get_env( + &self, + pst: &PartiallySignedTransaction, + input_index: usize, + network: &SimplicityNetwork, + ) -> Result>, ProgramError> { + (*self).get_env(pst, input_index, network) + } + + fn execute( + &self, + pst: &PartiallySignedTransaction, + witness: &WitnessValues, + input_index: usize, + network: &SimplicityNetwork, + ) -> Result<(Arc>, Value), ProgramError> { + (*self).execute(pst, witness, input_index, network) + } + + fn finalize( + &self, + pst: &PartiallySignedTransaction, + witness: &WitnessValues, + input_index: usize, + network: &SimplicityNetwork, + ) -> Result>, ProgramError> { + (*self).finalize(pst, witness, input_index, network) + } +} + impl Program { /// Creates a new instance of the struct with the provided source string and arguments. #[must_use] diff --git a/crates/sdk/src/signer/core.rs b/crates/sdk/src/signer/core.rs index c4d6acb0..bf4bed32 100644 --- a/crates/sdk/src/signer/core.rs +++ b/crates/sdk/src/signer/core.rs @@ -490,6 +490,13 @@ impl Signer { } fn sign_tx(&self, tx: &FinalTransaction) -> Result { + let pst = self.sign_tx_raw(tx)?; + + Ok(pst.extract_tx()?) + } + + /// Signs transaction in raw format for easy processing later in a format of `PartiallySignedTransaction`. + pub fn sign_tx_raw(&self, tx: &FinalTransaction) -> Result { let (mut pst, secrets) = tx.extract_pst(); let inputs = tx.inputs(); @@ -535,11 +542,11 @@ impl Signer { pst.inputs_mut()[index].final_script_witness = Some(vec![raw_sig, signed_witness.0.to_bytes()]); } } - - Ok(pst.extract_tx()?) + Ok(pst) } - fn get_signed_program_witness( + /// Signs and inserts a signature into appropriate witness value + pub fn get_signed_program_witness( &self, pst: &PartiallySignedTransaction, program: &dyn ProgramTrait, diff --git a/crates/sdk/src/transaction/final_transaction.rs b/crates/sdk/src/transaction/final_transaction.rs index 0742a51e..ce7b65ac 100644 --- a/crates/sdk/src/transaction/final_transaction.rs +++ b/crates/sdk/src/transaction/final_transaction.rs @@ -29,7 +29,7 @@ pub struct IssuanceDetails { } /// Represents the final input structure put into a `FinalTransaction` for processing. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FinalInput { /// Holds the base input data required for the operation. pub partial_input: PartialInput, @@ -139,7 +139,7 @@ impl FinalInput { } /// A struct representing a final (but not yet signed) transaction. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FinalTransaction { inputs: Vec, outputs: Vec, diff --git a/crates/sdk/src/transaction/partial_input.rs b/crates/sdk/src/transaction/partial_input.rs index 30cdf102..f16502db 100644 --- a/crates/sdk/src/transaction/partial_input.rs +++ b/crates/sdk/src/transaction/partial_input.rs @@ -1,3 +1,4 @@ +use std::fmt::{Debug, Formatter}; use std::sync::Arc; use simplicityhl::WitnessValues; @@ -68,6 +69,12 @@ pub struct ProgramInput { pub witness: Arc, } +impl Debug for ProgramInput { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.witness) + } +} + /// Represents an input designated for asset issuance or reissuance. #[derive(Clone, Debug)] pub enum IssuanceInput { diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index e270cec1..a4dbb34e 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -6,14 +6,14 @@ use simplicityhl::{Arguments, WitnessValues}; use smplx_sdk::program::ProgramError; use smplx_sdk::program::core::SimplexProgram; use smplx_sdk::provider::SimplicityNetwork; -use smplx_sdk::signer::{Signer, SignerError}; -use smplx_sdk::transaction::FinalTransaction; +use smplx_sdk::signer::Signer; use std::fmt::Debug; use std::sync::Arc; +#[derive(Clone)] pub struct FuzzContext { - pub signer: Option, - pub mock_provider: MockProvider, + pub signer: Arc>, + pub mock_provider: Arc, pub network: SimplicityNetwork, } @@ -24,27 +24,11 @@ pub trait FuzzableProgram: SimplexProgram { fn build_program(args: impl Into, network: &SimplicityNetwork) -> (Box

, Script); } -pub trait FuzzableBaseContextGen { - fn build_base_transaction( +pub trait FuzzStrategy: Debug { + fn get_strategy( &self, - network: &SimplicityNetwork, - args: Arguments, - wit: WitnessValues, - ) -> FinalTransaction; -} - -pub trait FuzzableContextGen { - fn modify_transaction( - &self, - signer: &Option, - ft: FinalTransaction, - args: &Arguments, - wit: &WitnessValues, - ) -> Result; -} - -pub trait ArgGenFuzzStrategy: Debug { - fn get_strategy(&self, test_context: &FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues)>; + test_context: FuzzContext, + ) -> BoxedStrategy<(Arguments, WitnessValues, PartiallySignedTransaction)>; } pub trait ProgramCheck { diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index d13cd649..d06b69d3 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -1,7 +1,4 @@ -use crate::mutantesting::core::{ - ArgGenFuzzStrategy, FuzzContext, FuzzableBaseContextGen, FuzzableContextGen, FuzzableProgram, ProgramCheck, - ProgramExecResult, -}; +use crate::mutantesting::core::{FuzzContext, FuzzStrategy, FuzzableProgram, ProgramCheck, ProgramExecResult}; use crate::mutantesting::provider::MockProvider; use proptest::prelude::TestCaseError; use simplicityhl::Arguments; @@ -10,12 +7,11 @@ use smplx_sdk::provider::SimplicityNetwork; use smplx_sdk::signer::Signer; use std::cell::RefCell; use std::marker::PhantomData; +use std::sync::Arc; pub struct SimplexFuzzEngineInner { pub(crate) fuzz_context: FuzzContext, - pub(crate) strategy_storage: Vec>>, - pub(crate) base_gen: Option>>, - pub(crate) mod_gen: Option>>, + pub(crate) strategy_storage: Option>>, } pub struct SimplexFuzzEngine { @@ -28,36 +24,34 @@ impl Default for FuzzContext { fn default() -> Self { let default_network = SimplicityNetwork::default_regtest(); Self { - signer: None, + signer: Arc::new(None), network: default_network, - mock_provider: MockProvider { + mock_provider: Arc::new(MockProvider { network: default_network, - }, + }), } } } impl FuzzContext { fn with_signer(&mut self, signer: Signer) { - self.signer = Some(signer); + self.signer = Arc::new(Some(signer)); } } -impl SimplexFuzzEngine +impl SimplexFuzzEngine where Args: ArgumentsTrait + Into + std::fmt::Debug + Clone + 'static, Wit: WitnessTrait + std::fmt::Debug + Clone + 'static, - FuzzProgram: FuzzableProgram + Clone + 'static, + Program: FuzzableProgram + Clone + 'static, { - pub fn from_config(mut config: proptest::test_runner::Config, _phantom: PhantomData) -> Self { + pub fn from_config(mut config: proptest::test_runner::Config, _phantom: PhantomData) -> Self { config.cases = 500; Self { runner: RefCell::new(proptest::test_runner::TestRunner::new(config)), inner: RefCell::new(SimplexFuzzEngineInner { fuzz_context: FuzzContext::default(), - strategy_storage: vec![], - base_gen: None, - mod_gen: None, + strategy_storage: None, }), _phantom, } @@ -77,61 +71,33 @@ where .with_signer(Signer::new(DEFAULT_TEST_MNEMONIC, Box::new(MockProvider { network }))); } - pub fn with_pset_base_gen_strategy(&self) - where - G: FuzzableBaseContextGen + Default + 'static, - { - self.inner.borrow_mut().base_gen = Some(Box::new(G::default())); - } - - pub fn with_pset_strategy(&self) - where - G: FuzzableContextGen + Default + 'static, - { - self.inner.borrow_mut().mod_gen = Some(Box::new(G::default())); - } - pub fn with_arg_gen_strategy(&self) where - S: ArgGenFuzzStrategy + Default + 'static, + S: FuzzStrategy + Default + 'static, { - self.inner.borrow_mut().strategy_storage.push(Box::new(S::default())); + self.inner.borrow_mut().strategy_storage = Some(Box::new(S::default())); } - pub fn run_with_check(&self, program_check_fn: impl ProgramCheck) { + pub fn run_with_check(self, program_check_fn: impl ProgramCheck) { let mut runner = self.runner.borrow_mut(); let inner = self.inner.borrow(); - let base_gen = inner.base_gen.as_ref().expect("Base gen strategy must be configured"); - let modifier = inner.mod_gen.as_ref().expect("Mod gen strategy must be configured"); + let strategy_gen = inner.strategy_storage.as_ref().expect("Strategy must be configured"); + let context = inner.fuzz_context.clone(); + let strategy = strategy_gen.get_strategy(context.clone()); - // TODO: remove strategies Vec, by now impossible to combine them, we can only use 1 - for strategy_gen in inner.strategy_storage.iter() { - let strategy = strategy_gen.get_strategy(&inner.fuzz_context); - match runner.run(&strategy, |(args, wit)| { - let context = &inner.fuzz_context; - let ft = base_gen.build_base_transaction(&context.network, args.clone(), wit.clone()); - // TODO: maybe make a couple of modification for one ft if non-default used? - let pst = modifier - .modify_transaction(&inner.fuzz_context.signer, ft, &args, &wit) - .unwrap(); + match runner.run(&strategy, |(args, wit, pst)| { + let (failure_program, _script) = Program::build_program(args.clone(), &context.network); - let (failure_program, _script) = FuzzProgram::build_program(args.clone(), &context.network); + let exec_result: ProgramExecResult = failure_program.get_program().execute(&pst, &wit, 0, &context.network); - // TODO: think how to provide a pset for a program execution environment - // let pst = PartiallySignedTransaction::from_tx(tx.clone()); - - let exec_result: ProgramExecResult = - failure_program.get_program().execute(&pst, &wit, 0, &context.network); - - match program_check_fn.call(context, &pst, &args, &wit, exec_result) { - Ok(_) => Ok(()), - Err(e) => Err(TestCaseError::fail(e)), - } - }) { - Ok(()) => (), - Err(e) => ::core::panic!("{}\n{}", e, runner), - }; - } + match program_check_fn.call(&context, &pst, &args, &wit, exec_result) { + Ok(_) => Ok(()), + Err(e) => Err(TestCaseError::fail(e)), + } + }) { + Ok(()) => (), + Err(e) => ::core::panic!("{}\n{}", e, runner), + }; } } diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index 8cfc336e..91d62780 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -4,8 +4,8 @@ pub mod provider; pub mod strategy; pub mod utils; -pub use proptest::test_runner::Config; +pub use proptest; pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; pub use engine::SimplexFuzzEngine; -pub use utils::generate_value_by_ty; +pub use utils::{generate_value_by_ty, sign_or_extract}; diff --git a/crates/test/src/mutantesting/strategy.rs b/crates/test/src/mutantesting/strategy.rs index 70370907..6e10f4ad 100644 --- a/crates/test/src/mutantesting/strategy.rs +++ b/crates/test/src/mutantesting/strategy.rs @@ -1,2 +1 @@ pub mod args; -pub mod pset; diff --git a/crates/test/src/mutantesting/strategy/args.rs b/crates/test/src/mutantesting/strategy/args.rs index 975be9e2..d7d0cff7 100644 --- a/crates/test/src/mutantesting/strategy/args.rs +++ b/crates/test/src/mutantesting/strategy/args.rs @@ -1,10 +1,8 @@ -use proptest::prelude::{BoxedStrategy, Strategy}; +use proptest::prelude::Strategy; use proptest::strategy::{NewTree, ValueTree}; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; -use crate::mutantesting::FuzzContext; -use crate::mutantesting::core::ArgGenFuzzStrategy; use proptest::prelude::Rng; use proptest::test_runner::{TestRng, TestRunner}; use simplicityhl::str::WitnessName; @@ -57,14 +55,6 @@ impl - ArgGenFuzzStrategy for Random -{ - fn get_strategy(&self, _test_context: &FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues)> { - Random::::default().boxed() - } -} - pub struct RandomValuePool { phantom_data: PhantomData<(Args, Wit)>, _value_pool: ValuePool, @@ -79,18 +69,6 @@ impl Default for RandomValuePool { } } -impl - ArgGenFuzzStrategy for RandomValuePool -{ - fn get_strategy(&self, _test_context: &FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues)> { - RandomValuePool:: { - phantom_data: Default::default(), - _value_pool: ValuePool::default(), - } - .boxed() - } -} - impl Debug for RandomValuePool { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { writeln!(f, "RandomValuePool trees...") diff --git a/crates/test/src/mutantesting/strategy/pset.rs b/crates/test/src/mutantesting/strategy/pset.rs deleted file mode 100644 index 1d78b0be..00000000 --- a/crates/test/src/mutantesting/strategy/pset.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::mutantesting::core::{FuzzableBaseContextGen, FuzzableContextGen, FuzzableProgram}; -use simplicityhl::elements::hashes::Hash; -use simplicityhl::elements::pset::PartiallySignedTransaction; -use simplicityhl::elements::{OutPoint, TxOut, Txid}; -use simplicityhl::{Arguments, WitnessValues}; -use smplx_sdk::provider::SimplicityNetwork; -use smplx_sdk::signer::{Signer, SignerError}; -use smplx_sdk::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; - -#[derive(Default)] -pub struct DefaultBaseContextGen {} - -impl> FuzzableBaseContextGen for DefaultBaseContextGen { - // TODO: move base transaction creation into Tree strategy - fn build_base_transaction( - &self, - network: &SimplicityNetwork, - args: Arguments, - wit: WitnessValues, - ) -> FinalTransaction { - const DEFAULT_FAUCET: u64 = 1 << 32; - - let mut ft = FinalTransaction::new(); - - let (failure_program, failure_script) = FuzzProgram::build_program(args, network); - - let txout = { - let mut r = TxOut::new_fee(DEFAULT_FAUCET, network.policy_asset()); - r.script_pubkey = failure_script; - r - }; - - ft.add_program_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::all_zeros(), 0), - txout, - secrets: None, - }), - ProgramInput::new(Box::new(failure_program.get_program().clone()), wit), - RequiredSignature::None, - ); - - ft - } -} - -#[derive(Default)] -pub struct DefaultContextGen {} - -impl> FuzzableContextGen for DefaultContextGen { - // todo: move into one strategy - fn modify_transaction( - &self, - _signer: &Option, - ft: FinalTransaction, - _args: &Arguments, - _wit: &WitnessValues, - ) -> Result { - Ok(ft.extract_pst().0) - // TODO: fix, incorrect witness_utxo extraction from sign_tx method - // idea - to sign and retrieve a valid finalized transaction to check, but not partial one without fees - // match signer { - // None => Ok(ft.extract_pst().0), - // Some(s) => Ok(s.finalize_offline(&ft)?.0), - // } - } -} diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/mutantesting/utils.rs index 612bcb2a..51f4d9b0 100644 --- a/crates/test/src/mutantesting/utils.rs +++ b/crates/test/src/mutantesting/utils.rs @@ -1,8 +1,11 @@ use rand::Rng; +use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::num::U256; use simplicityhl::types::{TypeInner, UIntType}; use simplicityhl::value::ValueConstructible; use simplicityhl::{ResolvedType, Value}; +use smplx_sdk::signer::{Signer, SignerError}; +use smplx_sdk::transaction::FinalTransaction; pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> Value { match ty.as_inner() { @@ -42,3 +45,14 @@ pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> _ => Value::unit(), } } + +#[inline] +pub fn sign_or_extract( + signer: &Option, + ft: &FinalTransaction, +) -> Result { + match signer.as_ref() { + None => Ok(ft.extract_pst().0), + Some(signer) => signer.sign_tx_raw(&ft), + } +} diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index a3a9b97b..ea2c524c 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -1,56 +1,169 @@ -use simplex::mutantesting; -use simplex::mutantesting::strategy::args::{Random, RandomValuePool}; -use simplex::mutantesting::strategy::pset::{DefaultBaseContextGen, DefaultContextGen}; -use simplex::mutantesting::{FuzzContext, ProgramCheck, ProgramExecResult, SimplexFuzzEngine}; -use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; -use simplex::simplicityhl::{Arguments, WitnessValues}; -use simplex_fixtures::artifacts::failure_test::FailureTestProgram; -use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; -use std::marker::PhantomData; - -struct FailureTestCheck; - -impl ProgramCheck for FailureTestCheck { - fn call( - &self, - _ctx: &FuzzContext, - _tx: &PartiallySignedTransaction, - _arguments: &Arguments, - _witness: &WitnessValues, - program_exec_result: ProgramExecResult, - ) -> Result<(), String> { - // dbg!(&program_exec_result); - let args = FailureTestArguments::from_arguments(_arguments).unwrap(); - let witness = FailureTestWitness::from_witness(_witness).unwrap(); - if args.failure_value == witness.cmp_value { - return Err(format!("Failed contract, {program_exec_result:?}")); +mod failure_test_prop { + use simplex::mutantesting; + use simplex::mutantesting::core::FuzzStrategy; + use simplex::mutantesting::proptest::prelude::Strategy; + use simplex::mutantesting::{ + FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, + }; + use simplex::program::SimplexProgram; + use simplex::simplicityhl::elements::hashes::Hash; + use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; + use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; + use simplex::simplicityhl::{Arguments, WitnessValues}; + use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; + use simplex_fixtures::artifacts::failure_test::FailureTestProgram; + use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; + use std::marker::PhantomData; + + struct FailureTestCheck; + + impl ProgramCheck for FailureTestCheck { + fn call( + &self, + _ctx: &FuzzContext, + _tx: &PartiallySignedTransaction, + _arguments: &Arguments, + _witness: &WitnessValues, + program_exec_result: ProgramExecResult, + ) -> Result<(), String> { + let args = FailureTestArguments::from_arguments(_arguments)?; + let witness = FailureTestWitness::from_witness(_witness)?; + if args.failure_value == witness.cmp_value { + return Err(format!("Failed contract, {program_exec_result:?}")); + } + if program_exec_result.is_err() { + println!("error: {program_exec_result:?}"); + return Err(format!("Failed contract, {program_exec_result:?}")); + } + Ok(()) } - if program_exec_result.is_err() { - println!("error: {program_exec_result:?}"); - return Err(format!("Failed contract, {program_exec_result:?}")); + } + + #[derive(Debug, Default)] + struct FailureGenStrategy; + impl FuzzStrategy for FailureGenStrategy { + fn get_strategy( + &self, + test_context: FuzzContext, + ) -> simplex::mutantesting::proptest::strategy::BoxedStrategy<( + Arguments, + WitnessValues, + PartiallySignedTransaction, + )> { + let init_strategy = (simplex::mutantesting::strategy::args::Random::< + FailureTestArguments, + FailureTestWitness, + >::default(),); + + let flat_strategy = init_strategy.prop_flat_map(move |args| { + ( + simplex::mutantesting::proptest::strategy::Just(args.0.0), + simplex::mutantesting::proptest::strategy::Just(args.0.1), + ) + }); + + let result_strategy = flat_strategy.prop_map(move |(args, wit)| { + const DEFAULT_FAUCET: u64 = 1 << 32; + + let mut ft = FinalTransaction::new(); + let (mutated_args, mutated_wit) = (args.clone(), wit.clone()); + + let (failure_program, failure_script) = FailureTestProgram::build_program(args, &test_context.network); + + let txout = { + let mut r = TxOut::new_fee(DEFAULT_FAUCET, test_context.network.policy_asset()); + r.script_pubkey = failure_script; + r + }; + + ft.add_program_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::all_zeros(), 0), + txout, + secrets: None, + }), + ProgramInput::new(Box::new(failure_program.get_program().clone()), wit), + RequiredSignature::None, + ); + + let signer = test_context.signer.as_ref(); + let pst = sign_or_extract(signer, &ft).unwrap(); + + (mutated_args, mutated_wit, pst) + }); + + result_strategy.boxed() + } + } + + #[derive(Debug, Default)] + struct FailureGenStrategyWithRandomPool; + impl FuzzStrategy for FailureGenStrategyWithRandomPool { + fn get_strategy( + &self, + test_context: FuzzContext, + ) -> simplex::mutantesting::proptest::strategy::BoxedStrategy<( + Arguments, + WitnessValues, + PartiallySignedTransaction, + )> { + let init_strategy = (simplex::mutantesting::strategy::args::RandomValuePool::< + FailureTestArguments, + FailureTestWitness, + >::default(),); + + let flat_strategy = init_strategy.prop_flat_map(move |args| { + ( + simplex::mutantesting::proptest::strategy::Just(args.0.0), + simplex::mutantesting::proptest::strategy::Just(args.0.1), + ) + }); + + let result_strategy = flat_strategy.prop_map(move |(args, wit)| { + const DEFAULT_FAUCET: u64 = 1 << 32; + + let mut ft = FinalTransaction::new(); + let (mutated_args, mutated_wit) = (args.clone(), wit.clone()); + + let (failure_program, failure_script) = FailureTestProgram::build_program(args, &test_context.network); + + let txout = { + let mut r = TxOut::new_fee(DEFAULT_FAUCET, test_context.network.policy_asset()); + r.script_pubkey = failure_script; + r + }; + + ft.add_program_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::all_zeros(), 0), + txout, + secrets: None, + }), + ProgramInput::new(Box::new(failure_program.get_program().clone()), wit), + RequiredSignature::None, + ); + + let signer = test_context.signer.as_ref(); + let pst = sign_or_extract(signer, &ft).unwrap(); + + (mutated_args, mutated_wit, pst) + }); + + result_strategy.boxed() } - Ok(()) } -} -mod tests { - use super::*; #[ignore] #[test] - fn possible_interface() -> anyhow::Result<()> { + fn possible_interface_failure_program() -> anyhow::Result<()> { let fuzz_engine = SimplexFuzzEngine::::from_config( - mutantesting::Config::default(), + mutantesting::proptest::test_runner::Config::default(), PhantomData, ); - // TODO: REMOVE signer, seems to be redundant due to sign_tx method signature (it elides witness_utxo) fuzz_engine.with_default_signer(); - // TODO: move 2 lines into 1 strategy - fuzz_engine.with_pset_base_gen_strategy::(); - fuzz_engine.with_pset_strategy::(); - - fuzz_engine.with_arg_gen_strategy::>(); + fuzz_engine.with_arg_gen_strategy::(); fuzz_engine.run_with_check(FailureTestCheck); @@ -59,23 +172,183 @@ mod tests { #[ignore] #[test] - fn possible_interface_2() -> anyhow::Result<()> { + fn possible_interface_failure_program_with_pool() -> anyhow::Result<()> { let fuzz_engine = SimplexFuzzEngine::::from_config( - mutantesting::Config::default(), + mutantesting::proptest::test_runner::Config::default(), PhantomData, ); - // TODO: REMOVE signer, seems to be redundant due to sign_tx method signature (it elides witness_utxo) fuzz_engine.with_default_signer(); - // TODO: move 2 lines into 1 strategy - fuzz_engine.with_pset_base_gen_strategy::(); - fuzz_engine.with_pset_strategy::(); - - fuzz_engine.with_arg_gen_strategy::>(); + fuzz_engine.with_arg_gen_strategy::(); fuzz_engine.run_with_check(FailureTestCheck); Ok(()) } } + +mod simple_storage_test_prop { + use simplex::mutantesting; + use simplex::mutantesting::core::FuzzStrategy; + use simplex::mutantesting::proptest::prelude::Strategy; + use simplex::mutantesting::{ + FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, + }; + use simplex::program::SimplexProgram; + use simplex::program::{ArgumentsTrait, WitnessTrait}; + use simplex::simplicityhl::elements::AssetId; + use simplex::simplicityhl::elements::hashes::Hash; + use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; + use simplex::simplicityhl::elements::pset::serialize::Serialize; + use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; + use simplex::simplicityhl::{Arguments, WitnessValues}; + use simplex::transaction::PartialOutput; + use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; + use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; + use simplex_fixtures::artifacts::simple_storage::derived_simple_storage::{ + SimpleStorageArguments, SimpleStorageWitness, + }; + use std::marker::PhantomData; + + pub struct SimpleStorageCheck; + + impl ProgramCheck for SimpleStorageCheck { + fn call( + &self, + _ctx: &FuzzContext, + _tx: &PartiallySignedTransaction, + _arguments: &Arguments, + _witness: &WitnessValues, + program_exec_result: ProgramExecResult, + ) -> Result<(), String> { + if let Err(x) = program_exec_result { + Err(format!("some error: {x:?}")) + } else { + Ok(()) + } + } + } + + #[derive(Debug, Default)] + struct SimpleStorageStrategy; + + impl FuzzStrategy for SimpleStorageStrategy { + fn get_strategy( + &self, + test_context: FuzzContext, + ) -> simplex::mutantesting::proptest::strategy::BoxedStrategy<( + Arguments, + WitnessValues, + PartiallySignedTransaction, + )> { + let init_strategy = ( + simplex::mutantesting::strategy::args::Random::::default( + ), + 0..(u32::MAX as u64), + ); + + let flat_strategy = init_strategy.prop_flat_map(move |((args, wit), old_value)| { + ( + simplex::mutantesting::proptest::strategy::Just(args), + simplex::mutantesting::proptest::strategy::Just(wit), + simplex::mutantesting::proptest::strategy::Just(old_value), + old_value..(u32::MAX as u64), + ) + }); + + let result_strategy = flat_strategy.prop_map(move |(args, wit, old_value, new_value)| { + let mut ft = FinalTransaction::new(); + let mut args_typed = SimpleStorageArguments::from_arguments(&args).unwrap(); + let mut wit_typed = SimpleStorageWitness::from_witness(&wit).unwrap(); + let signer = test_context.signer.as_ref(); + + wit_typed.new_value = new_value; + + { + let mut slot: [u8; 32] = Default::default(); + slot.copy_from_slice(&test_context.network.policy_asset().serialize()); + args_typed.slot_id = slot; + args_typed.user = signer.as_ref().unwrap().get_schnorr_public_key().serialize(); + } + let modified_args = args_typed.build_arguments(); + let (_fuzz_program, old_storage_args_script) = + SimpleStorageProgram::build_program(modified_args.clone(), &test_context.network); + + ft.add_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::from_slice(&[1; 32]).unwrap(), 0), + txout: { + let mut r = TxOut::new_fee(old_value, test_context.network.policy_asset()); + r.script_pubkey = old_storage_args_script.clone(); + r + }, + secrets: None, + }), + RequiredSignature::None, + ); + + ft.add_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::from_slice(&[2; 32]).unwrap(), 1), + txout: { + let mut r = TxOut::new_fee(1, AssetId::default()); + r.script_pubkey = old_storage_args_script.clone(); + r + }, + secrets: None, + }), + RequiredSignature::None, + ); + + ft.add_output(PartialOutput { + script_pubkey: old_storage_args_script.clone(), + amount: new_value, + asset: test_context.network.policy_asset(), + blinding_key: None, + }); + ft.add_output(PartialOutput { + script_pubkey: old_storage_args_script, + amount: 0, + asset: Default::default(), + blinding_key: None, + }); + + let pst = sign_or_extract(signer, &ft).unwrap(); + let wit_signed = signer + .as_ref() + .unwrap() + .get_signed_program_witness( + &pst, + SimpleStorageProgram::new(modified_args.clone()).get_program(), + &wit_typed.build_witness(), + "USER_SIGNATURE", + &[], + 0, + ) + .unwrap(); + + (modified_args, wit_signed, pst) + }); + + result_strategy.boxed() + } + } + + #[ignore] + #[test] + fn possible_interface_simple_program() -> anyhow::Result<()> { + let fuzz_engine = + SimplexFuzzEngine::::from_config( + mutantesting::proptest::test_runner::Config::default(), + PhantomData, + ); + + fuzz_engine.with_default_signer(); + fuzz_engine.with_arg_gen_strategy::(); + + fuzz_engine.run_with_check(SimpleStorageCheck); + + Ok(()) + } +} From 76922f85845770c80be2f7cc7e265c1793103145 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 17 Jun 2026 11:54:49 +0300 Subject: [PATCH 08/21] macros: add ProgramFactory trait for programs instantiating * add program trait helpers generation in the macros * fix little warnings * remove SimplexProgram * change dependence onto AsRef trait instead of SimplexProgram --- crates/build/src/macros/codegen.rs | 42 ++++++-------------------- crates/build/src/macros/core.rs | 14 ++++----- crates/sdk/src/program/core.rs | 33 ++++++-------------- crates/sdk/src/program/mod.rs | 2 +- crates/sdk/src/signer/core.rs | 14 ++++++++- crates/test/src/mutantesting/core.rs | 13 ++++++-- crates/test/src/mutantesting/engine.rs | 8 ++++- crates/test/src/mutantesting/utils.rs | 2 +- fixtures/tests/prop_testing.rs | 8 ++--- 9 files changed, 61 insertions(+), 75 deletions(-) diff --git a/crates/build/src/macros/codegen.rs b/crates/build/src/macros/codegen.rs index 2de8d814..d9090599 100644 --- a/crates/build/src/macros/codegen.rs +++ b/crates/build/src/macros/codegen.rs @@ -27,7 +27,7 @@ pub struct GeneratedWitnessTokens { pub struct_impl: proc_macro2::TokenStream, } -pub struct GeneratedMutanTestingTokens { +pub struct GeneratedProgramTraitHelperTokens { pub imports: proc_macro2::TokenStream, pub helper_impls: proc_macro2::TokenStream, } @@ -65,48 +65,26 @@ impl SimfContractMeta { } /// Generates code necessary for creating mutant testing using simplex. - pub fn generate_mutantesting_impl(&self) -> syn::Result { + pub fn generate_program_trait_helpers_impl(&self) -> syn::Result { let args_struct_name = &self.args_struct.struct_name; let program_name = &self.program_struct_name; - let fuzzable_program_impl = quote! { - impl FuzzableProgram<#program_name> for #program_name { - fn build_program( - args: impl Into, - network: &SimplicityNetwork, - ) -> (Box<#program_name>, Script) { - let prog = #program_name::new(args); - let script = prog.get_script_pubkey(network); - (Box::new(prog), script) - } - } - - impl SimplexProgram for #program_name { - fn get_program(&self) -> &Program { - &self.program - } - - fn get_compiled_program(args: Arguments) -> CompiledProgram { - #program_name::new(args).program.load().unwrap() - } - - fn get_mut_program(&mut self) -> &mut Program { - &mut self.program + let program_helpers_impl = quote! { + impl ProgramFactory<#program_name> for #program_name { + fn instantiate_program(args: impl Into) -> Box<#program_name> { + Box::new(#program_name::new(args)) } } }; - Ok(GeneratedMutanTestingTokens { + Ok(GeneratedProgramTraitHelperTokens { imports: quote! { use super::{super::#program_name, #args_struct_name}; - use simplex::mutantesting::FuzzableProgram; - use simplex::provider::SimplicityNetwork; - use simplex::program::{Program, SimplexProgram}; - use simplex::simplicityhl::{Arguments, CompiledProgram}; - use simplex::simplicityhl::elements::Script; + use simplex::program::{Program, ProgramFactory}; + use simplex::simplicityhl::{Arguments}; }, helper_impls: quote! { - #fuzzable_program_impl + #program_helpers_impl }, }) } diff --git a/crates/build/src/macros/core.rs b/crates/build/src/macros/core.rs index 2e028179..e50d29b2 100644 --- a/crates/build/src/macros/core.rs +++ b/crates/build/src/macros/core.rs @@ -7,7 +7,7 @@ use simplicityhl::ast::ElementsJetHinter; use simplicityhl::{AbiMeta, TemplateProgram, UnstableFeatures}; use super::codegen::{ - GeneratedArgumentTokens, GeneratedMutanTestingTokens, GeneratedWitnessTokens, SimfContractMeta, + GeneratedArgumentTokens, GeneratedProgramTraitHelperTokens, GeneratedWitnessTokens, SimfContractMeta, convert_contract_name_to_contract_module, }; use super::parse::{SimfContent, SynFilePath}; @@ -28,7 +28,7 @@ fn expand_inner(simf_content: SimfContent, meta: AbiMeta) -> Result Result Result> { ) } -fn construct_mutantesting_helpers(derived_meta: &SimfContractMeta) -> syn::Result { - let GeneratedMutanTestingTokens { imports, helper_impls } = derived_meta.generate_mutantesting_impl()?; +fn construct_program_trait_helpers(derived_meta: &SimfContractMeta) -> syn::Result { + let GeneratedProgramTraitHelperTokens { imports, helper_impls } = + derived_meta.generate_program_trait_helpers_impl()?; - // TODO: move this feature under flag if is needed Ok(quote! { - mod mutantesting { + mod program_helpers { #imports #helper_impls diff --git a/crates/sdk/src/program/core.rs b/crates/sdk/src/program/core.rs index 53f79998..5e50d0cb 100644 --- a/crates/sdk/src/program/core.rs +++ b/crates/sdk/src/program/core.rs @@ -76,29 +76,6 @@ pub trait ProgramTrait: DynClone { ) -> Result>, ProgramError>; } -/// A trait that defines and additional functionality required for handling a Simplicity-based -/// program for a `Program` value wrapper generated by `include_simf!` macro. -pub trait SimplexProgram { - /// Generates the script public key (scriptPubKey) associated with the Simplicity program. - fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { - self.get_program().get_script_pubkey(network) - } - - /// Computes the script hash for the current program based on the provided network. - fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { - self.get_program().get_script_hash(network) - } - - /// Retrieves a reference to the program associated with the current instance. - fn get_program(&self) -> &Program; - - /// Compiles the provided program based on the given arguments and returns the compiled program. - fn get_compiled_program(args: Arguments) -> CompiledProgram; - - /// Returns a mutable reference to the `Program` instance. - fn get_mut_program(&mut self) -> &mut Program; -} - /// Represents a program structure containing its source, a public key, arguments, and associated storage. /// /// Abstraction giving the power to execute Simplicity contracts without specifying any additional parameters. @@ -242,7 +219,7 @@ impl ProgramTrait for &T { witness: &WitnessValues, input_index: usize, network: &SimplicityNetwork, - ) -> Result<(Arc>, Value), ProgramError> { + ) -> Result<(Arc, Value), ProgramError> { (*self).execute(pst, witness, input_index, network) } @@ -445,6 +422,14 @@ impl Program { } } +/// A trait for creating instances of a program. The `ProgramFactory` trait defines a mechanism +/// for constructing and returning a program instance of a type that implements `AsRef`. +/// Even only by generic struct name we have a possibility to create an instance of a program. +pub trait ProgramFactory + Sized> { + /// Instantiates a program instance with the given arguments. + fn instantiate_program(args: impl Into) -> Box

; +} + #[cfg(test)] mod tests { use simplicityhl::{ diff --git a/crates/sdk/src/program/mod.rs b/crates/sdk/src/program/mod.rs index da9927a6..2246c34a 100644 --- a/crates/sdk/src/program/mod.rs +++ b/crates/sdk/src/program/mod.rs @@ -10,7 +10,7 @@ pub mod logger; pub mod witness; pub use arguments::{ArgumentsTrait, RandomArguments}; -pub use core::{Program, ProgramTrait, SimplexProgram}; +pub use core::{Program, ProgramFactory, ProgramTrait}; pub use error::ProgramError; pub use simplicityhl::tracker::TrackerLogLevel; pub use witness::{RandomWitness, WitnessTrait}; diff --git a/crates/sdk/src/signer/core.rs b/crates/sdk/src/signer/core.rs index bf4bed32..f455f055 100644 --- a/crates/sdk/src/signer/core.rs +++ b/crates/sdk/src/signer/core.rs @@ -496,6 +496,12 @@ impl Signer { } /// Signs transaction in raw format for easy processing later in a format of `PartiallySignedTransaction`. + /// + /// # Errors + /// Returns a `SignerError` if we have an error in singing and constructing program witness. + /// + /// # Panics + /// Throws a panic if we failed to sign a program witness. pub fn sign_tx_raw(&self, tx: &FinalTransaction) -> Result { let (mut pst, secrets) = tx.extract_pst(); let inputs = tx.inputs(); @@ -545,7 +551,12 @@ impl Signer { Ok(pst) } - /// Signs and inserts a signature into appropriate witness value + /// Signs and inserts a signature into appropriate witness value. + /// + /// # Errors + /// Returns a `SignerError` if signing the program fails, if the witness types cannot be + /// retrieved from the program, if `witness_name` is not present among the program's + /// witness fields, or if injecting the signature into the witness value at `sig_path` fails. pub fn get_signed_program_witness( &self, pst: &PartiallySignedTransaction, @@ -566,6 +577,7 @@ impl Signer { .get(&WitnessName::from_str_unchecked(witness_name)) .ok_or(SignerError::WtnsFieldNotFound(witness_name.to_string()))?; + #[allow(clippy::missing_panics_doc)] let local_wtns = Arc::new( witness .get(&WitnessName::from_str_unchecked(witness_name)) diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index a4dbb34e..8c4dddc7 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -3,8 +3,7 @@ use proptest::prelude::BoxedStrategy; use simplicityhl::elements::Script; use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::{Arguments, WitnessValues}; -use smplx_sdk::program::ProgramError; -use smplx_sdk::program::core::SimplexProgram; +use smplx_sdk::program::{Program, ProgramError, ProgramFactory}; use smplx_sdk::provider::SimplicityNetwork; use smplx_sdk::signer::Signer; use std::fmt::Debug; @@ -20,10 +19,18 @@ pub struct FuzzContext { use simplicityhl::simplicity::{RedeemNode, Value}; pub type ProgramExecResult = Result<(Arc, Value), ProgramError>; -pub trait FuzzableProgram: SimplexProgram { +pub trait FuzzableProgram>: AsRef { fn build_program(args: impl Into, network: &SimplicityNetwork) -> (Box

, Script); } +impl + ProgramFactory

> FuzzableProgram

for P { + fn build_program(args: impl Into, network: &SimplicityNetwork) -> (Box

, Script) { + let prog = P::instantiate_program(args); + let script = prog.as_ref().as_ref().get_script_pubkey(network); + (prog, script) + } +} + pub trait FuzzStrategy: Debug { fn get_strategy( &self, diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index d06b69d3..ce2cc214 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -20,6 +20,7 @@ pub struct SimplexFuzzEngine { _phantom: PhantomData, } +#[allow(clippy::arc_with_non_send_sync)] impl Default for FuzzContext { fn default() -> Self { let default_network = SimplicityNetwork::default_regtest(); @@ -34,6 +35,7 @@ impl Default for FuzzContext { } impl FuzzContext { + #[allow(clippy::arc_with_non_send_sync)] fn with_signer(&mut self, signer: Signer) { self.signer = Arc::new(Some(signer)); } @@ -89,7 +91,11 @@ where match runner.run(&strategy, |(args, wit, pst)| { let (failure_program, _script) = Program::build_program(args.clone(), &context.network); - let exec_result: ProgramExecResult = failure_program.get_program().execute(&pst, &wit, 0, &context.network); + let exec_result: ProgramExecResult = + failure_program + .as_ref() + .as_ref() + .execute(&pst, &wit, 0, &context.network); match program_check_fn.call(&context, &pst, &args, &wit, exec_result) { Ok(_) => Ok(()), diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/mutantesting/utils.rs index 51f4d9b0..d3bfabe1 100644 --- a/crates/test/src/mutantesting/utils.rs +++ b/crates/test/src/mutantesting/utils.rs @@ -53,6 +53,6 @@ pub fn sign_or_extract( ) -> Result { match signer.as_ref() { None => Ok(ft.extract_pst().0), - Some(signer) => signer.sign_tx_raw(&ft), + Some(signer) => signer.sign_tx_raw(ft), } } diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index ea2c524c..1e8ab08d 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -5,7 +5,6 @@ mod failure_test_prop { use simplex::mutantesting::{ FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, }; - use simplex::program::SimplexProgram; use simplex::simplicityhl::elements::hashes::Hash; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; @@ -82,7 +81,7 @@ mod failure_test_prop { txout, secrets: None, }), - ProgramInput::new(Box::new(failure_program.get_program().clone()), wit), + ProgramInput::new(Box::new(failure_program.as_ref().as_ref().clone()), wit), RequiredSignature::None, ); @@ -139,7 +138,7 @@ mod failure_test_prop { txout, secrets: None, }), - ProgramInput::new(Box::new(failure_program.get_program().clone()), wit), + ProgramInput::new(Box::new(failure_program.as_ref().as_ref().clone()), wit), RequiredSignature::None, ); @@ -195,7 +194,6 @@ mod simple_storage_test_prop { use simplex::mutantesting::{ FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, }; - use simplex::program::SimplexProgram; use simplex::program::{ArgumentsTrait, WitnessTrait}; use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::hashes::Hash; @@ -320,7 +318,7 @@ mod simple_storage_test_prop { .unwrap() .get_signed_program_witness( &pst, - SimpleStorageProgram::new(modified_args.clone()).get_program(), + SimpleStorageProgram::new(modified_args.clone()).as_ref(), &wit_typed.build_witness(), "USER_SIGNATURE", &[], From abd1ac497e64e31c4a8ba388d9596895ea700393 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 17 Jun 2026 12:53:53 +0300 Subject: [PATCH 09/21] test: remove MockProvider * fix warning with failure case persistence in the proptest --- crates/test/src/mutantesting/core.rs | 5 +- crates/test/src/mutantesting/engine.rs | 18 +++---- crates/test/src/mutantesting/mod.rs | 1 - crates/test/src/mutantesting/provider.rs | 64 ------------------------ fixtures/tests/prop_testing.rs | 31 ++++++++++-- 5 files changed, 38 insertions(+), 81 deletions(-) delete mode 100644 crates/test/src/mutantesting/provider.rs diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index 8c4dddc7..87c120fc 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -1,10 +1,9 @@ -use crate::mutantesting::provider::MockProvider; use proptest::prelude::BoxedStrategy; use simplicityhl::elements::Script; use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::{Arguments, WitnessValues}; use smplx_sdk::program::{Program, ProgramError, ProgramFactory}; -use smplx_sdk::provider::SimplicityNetwork; +use smplx_sdk::provider::{ProviderTrait, SimplicityNetwork}; use smplx_sdk::signer::Signer; use std::fmt::Debug; use std::sync::Arc; @@ -12,7 +11,7 @@ use std::sync::Arc; #[derive(Clone)] pub struct FuzzContext { pub signer: Arc>, - pub mock_provider: Arc, + pub mock_provider: Arc, pub network: SimplicityNetwork, } diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index ce2cc214..3c828943 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -1,9 +1,8 @@ use crate::mutantesting::core::{FuzzContext, FuzzStrategy, FuzzableProgram, ProgramCheck, ProgramExecResult}; -use crate::mutantesting::provider::MockProvider; use proptest::prelude::TestCaseError; use simplicityhl::Arguments; use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, WitnessTrait}; -use smplx_sdk::provider::SimplicityNetwork; +use smplx_sdk::provider::{EsploraProvider, SimplicityNetwork}; use smplx_sdk::signer::Signer; use std::cell::RefCell; use std::marker::PhantomData; @@ -27,9 +26,7 @@ impl Default for FuzzContext { Self { signer: Arc::new(None), network: default_network, - mock_provider: Arc::new(MockProvider { - network: default_network, - }), + mock_provider: Arc::new(get_default_provider(default_network)), } } } @@ -40,6 +37,9 @@ impl FuzzContext { self.signer = Arc::new(Some(signer)); } } +fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { + EsploraProvider::new("default_web_page.com".into(), default_network) +} impl SimplexFuzzEngine where @@ -67,10 +67,10 @@ where const DEFAULT_TEST_MNEMONIC: &str = "exist carry drive collect lend cereal occur much tiger just involve mean"; let network = self.inner.borrow().fuzz_context.network; - self.inner - .borrow_mut() - .fuzz_context - .with_signer(Signer::new(DEFAULT_TEST_MNEMONIC, Box::new(MockProvider { network }))); + self.inner.borrow_mut().fuzz_context.with_signer(Signer::new( + DEFAULT_TEST_MNEMONIC, + Box::new(get_default_provider(network)), + )); } pub fn with_arg_gen_strategy(&self) diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index 91d62780..044ef1a2 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -1,6 +1,5 @@ pub mod core; pub mod engine; -pub mod provider; pub mod strategy; pub mod utils; diff --git a/crates/test/src/mutantesting/provider.rs b/crates/test/src/mutantesting/provider.rs deleted file mode 100644 index 9777a07f..00000000 --- a/crates/test/src/mutantesting/provider.rs +++ /dev/null @@ -1,64 +0,0 @@ -use simplicityhl::elements::{Address, Script, Transaction, Txid}; -use smplx_sdk::provider::{ProviderError, ProviderTrait, SimplicityNetwork}; -use smplx_sdk::transaction::{TxReceipt, UTXO}; -use std::collections::HashMap; - -pub struct MockProvider { - pub network: SimplicityNetwork, -} - -impl MockProvider { - pub fn new(network: SimplicityNetwork) -> Self { - Self { network } - } -} - -impl ProviderTrait for MockProvider { - fn get_network(&self) -> &SimplicityNetwork { - &self.network - } - - fn broadcast_transaction(&self, _tx: &Transaction) -> Result, ProviderError> { - unimplemented!("No network access needed for tests") - } - - fn wait(&self, _txid: &Txid) -> Result<(), ProviderError> { - unimplemented!("No network access needed for tests") - } - - fn fetch_tip_height(&self) -> Result { - unimplemented!("No network access needed for tests") - } - - fn fetch_tip_block_hash(&self) -> Result { - unimplemented!("No network access needed for tests") - } - - fn fetch_tip_timestamp(&self) -> Result { - unimplemented!("No network access needed for tests") - } - - fn fetch_block_hash_at_height(&self, _block_height: u32) -> Result { - unimplemented!("No network access needed for tests") - } - - fn fetch_block_txids(&self, _block_hash: &str) -> Result, ProviderError> { - unimplemented!("No network access needed for tests") - } - - fn fetch_transaction(&self, _txid: &Txid) -> Result { - unimplemented!("No network access needed for tests") - } - - fn fetch_address_utxos(&self, _address: &Address) -> Result, ProviderError> { - unimplemented!("No network access needed for tests") - } - - fn fetch_scripthash_utxos(&self, _script: &Script) -> Result, ProviderError> { - unimplemented!("No network access needed for tests") - } - - fn fetch_fee_estimates(&self) -> Result, ProviderError> { - Ok(HashMap::new()) - } -} diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 1e8ab08d..7e0e1da0 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -155,9 +155,17 @@ mod failure_test_prop { #[ignore] #[test] fn possible_interface_failure_program() -> anyhow::Result<()> { + let mut config = mutantesting::proptest::test_runner::Config::with_source_file(file!()); + config.test_name = ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_failure_program) + )); + config.source_file = Some(dbg!(concat!(stringify!(CARGO_MANIFEST_DIR), "/src/somefile.txt"))); + let fuzz_engine = SimplexFuzzEngine::::from_config( - mutantesting::proptest::test_runner::Config::default(), + config, PhantomData, ); @@ -172,9 +180,17 @@ mod failure_test_prop { #[ignore] #[test] fn possible_interface_failure_program_with_pool() -> anyhow::Result<()> { + let mut config = mutantesting::proptest::test_runner::Config::with_source_file(file!()); + config.test_name = ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_failure_program_with_pool) + )); + config.source_file = Some(dbg!(concat!(stringify!(CARGO_MANIFEST_DIR), "/src/somefile.txt"))); + let fuzz_engine = SimplexFuzzEngine::::from_config( - mutantesting::proptest::test_runner::Config::default(), + config, PhantomData, ); @@ -333,12 +349,19 @@ mod simple_storage_test_prop { } } - #[ignore] #[test] fn possible_interface_simple_program() -> anyhow::Result<()> { + let mut config = mutantesting::proptest::test_runner::Config::default(); + config.test_name = ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_simple_program) + )); + config.source_file = Some(dbg!(concat!(stringify!(CARGO_MANIFEST_DIR), "/src/somefile.txt"))); + let fuzz_engine = SimplexFuzzEngine::::from_config( - mutantesting::proptest::test_runner::Config::default(), + config, PhantomData, ); From e7db4f52035b624a38ef28c7586d3e7906094302 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 17 Jun 2026 13:27:14 +0300 Subject: [PATCH 10/21] fixtures: fix and adapt config --- fixtures/tests/prop_testing.rs | 67 ++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 7e0e1da0..355561aa 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -142,8 +142,7 @@ mod failure_test_prop { RequiredSignature::None, ); - let signer = test_context.signer.as_ref(); - let pst = sign_or_extract(signer, &ft).unwrap(); + let pst = ft.extract_pst().0; (mutated_args, mutated_wit, pst) }); @@ -155,13 +154,20 @@ mod failure_test_prop { #[ignore] #[test] fn possible_interface_failure_program() -> anyhow::Result<()> { - let mut config = mutantesting::proptest::test_runner::Config::with_source_file(file!()); - config.test_name = ::core::option::Option::Some(::core::concat!( - ::core::module_path!(), - "::", - ::core::stringify!(possible_interface_failure_program) - )); - config.source_file = Some(dbg!(concat!(stringify!(CARGO_MANIFEST_DIR), "/src/somefile.txt"))); + let config = mutantesting::proptest::test_runner::Config { + test_name: ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_failure_program) + )), + source_file: Some(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/", + stringify!(possible_interface_failure_program), + ".txt" + )), + ..Default::default() + }; let fuzz_engine = SimplexFuzzEngine::::from_config( @@ -180,13 +186,20 @@ mod failure_test_prop { #[ignore] #[test] fn possible_interface_failure_program_with_pool() -> anyhow::Result<()> { - let mut config = mutantesting::proptest::test_runner::Config::with_source_file(file!()); - config.test_name = ::core::option::Option::Some(::core::concat!( - ::core::module_path!(), - "::", - ::core::stringify!(possible_interface_failure_program_with_pool) - )); - config.source_file = Some(dbg!(concat!(stringify!(CARGO_MANIFEST_DIR), "/src/somefile.txt"))); + let config = mutantesting::proptest::test_runner::Config { + test_name: ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_failure_program_with_pool) + )), + source_file: Some(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/", + stringify!(possible_interface_failure_program_with_pool), + ".txt" + )), + ..Default::default() + }; let fuzz_engine = SimplexFuzzEngine::::from_config( @@ -349,15 +362,23 @@ mod simple_storage_test_prop { } } + #[ignore] #[test] fn possible_interface_simple_program() -> anyhow::Result<()> { - let mut config = mutantesting::proptest::test_runner::Config::default(); - config.test_name = ::core::option::Option::Some(::core::concat!( - ::core::module_path!(), - "::", - ::core::stringify!(possible_interface_simple_program) - )); - config.source_file = Some(dbg!(concat!(stringify!(CARGO_MANIFEST_DIR), "/src/somefile.txt"))); + let config = mutantesting::proptest::test_runner::Config { + test_name: ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_simple_program) + )), + source_file: Some(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/", + stringify!(possible_interface_simple_program), + ".txt" + )), + ..Default::default() + }; let fuzz_engine = SimplexFuzzEngine::::from_config( From 8ee70975633aa069c5871546cf4b2331a4850faa Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Thu, 18 Jun 2026 10:12:09 +0300 Subject: [PATCH 11/21] deps: remove unused `glob` and `rand-core` dependency from simplex-build --- Cargo.lock | 2 -- crates/build/Cargo.toml | 2 -- examples/basic/Cargo.lock | 8 -------- fixtures/Cargo.lock | 8 -------- 4 files changed, 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e365693..ccc44cfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1492,13 +1492,11 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "smplx-build" version = "0.0.7" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", "proc-macro2", "quote", - "rand_core 0.9.5", "serde", "simplicityhl", "syn", diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index 452d9a84..d043d2c7 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -13,12 +13,10 @@ thiserror = { workspace = true } toml = { workspace = true } serde = { workspace = true } simplicityhl = { workspace = true } -rand_core = { workspace = true } syn = { version = "2.0.114", default-features = false, features = ["proc-macro", "full", "parsing", "derive", "clone-impls", "extra-traits", "printing"] } proc-macro2 = { version = "1.0.106", features = ["span-locations"] } quote = { version = "1.0.44" } pathdiff = { version = "0.2.3" } prettyplease = { version = "0.2.37" } -glob = { version = "0.3.3" } globwalk = { version = "0.9.1" } diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 2de09292..32d38de3 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -625,12 +625,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "globset" version = "0.4.18" @@ -1416,13 +1410,11 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "smplx-build" version = "0.0.7" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", "proc-macro2", "quote", - "rand_core 0.9.5", "serde", "simplicityhl", "syn", diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index fa4d1e62..71c37f1b 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -625,12 +625,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "globset" version = "0.4.18" @@ -1416,13 +1410,11 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "smplx-build" version = "0.0.7" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", "proc-macro2", "quote", - "rand_core 0.9.5", "serde", "simplicityhl", "syn", From 82d1e49c55aef86a895fef136b52ae435f0745aa Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Thu, 18 Jun 2026 16:29:36 +0300 Subject: [PATCH 12/21] proptest: edit the interface, now user can declare only Strategy and UserFuzzStrategy * user also has choice to declare with extension or without --- crates/test/src/mutantesting/core.rs | 76 +++++- crates/test/src/mutantesting/engine.rs | 94 ++++--- fixtures/tests/prop_testing.rs | 357 ++++++++++--------------- 3 files changed, 274 insertions(+), 253 deletions(-) diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index 87c120fc..f0a7407c 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -1,10 +1,14 @@ -use proptest::prelude::BoxedStrategy; +use proptest::prelude::{BoxedStrategy, Strategy}; use simplicityhl::elements::Script; use simplicityhl::elements::pset::PartiallySignedTransaction; +use simplicityhl::simplicity::{RedeemNode, Value}; use simplicityhl::{Arguments, WitnessValues}; -use smplx_sdk::program::{Program, ProgramError, ProgramFactory}; + +use smplx_sdk::program::{Program, ProgramError, ProgramFactory, RandomArguments, RandomWitness}; use smplx_sdk::provider::{ProviderTrait, SimplicityNetwork}; use smplx_sdk::signer::Signer; +use smplx_sdk::transaction::FinalTransaction; + use std::fmt::Debug; use std::sync::Arc; @@ -15,9 +19,18 @@ pub struct FuzzContext { pub network: SimplicityNetwork, } -use simplicityhl::simplicity::{RedeemNode, Value}; - pub type ProgramExecResult = Result<(Arc, Value), ProgramError>; + +pub type GenStrategyExt = ( + BoxedStrategy<((Arguments, WitnessValues), T)>, + Box>, +); + +pub type GenStrategy = ( + BoxedStrategy<(Arguments, WitnessValues)>, + Box>, +); + pub trait FuzzableProgram>: AsRef { fn build_program(args: impl Into, network: &SimplicityNetwork) -> (Box

, Script); } @@ -31,10 +44,60 @@ impl + ProgramFactory

> FuzzableProgram

for P { } pub trait FuzzStrategy: Debug { - fn get_strategy( + fn get_strategy(self, test_context: FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues, FinalTransaction)>; +} + +impl FuzzStrategy + for ( + BoxedStrategy<(Arguments, WitnessValues)>, + Box>, + ) +{ + fn get_strategy(self, test_context: FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues, FinalTransaction)> { + let (init_strategy, pst_strategy) = self; + + let result_strategy = init_strategy + .prop_map(move |(args, wit)| pst_strategy.gen_final_transaction(test_context.clone(), args, wit)); + + result_strategy.boxed() + } +} + +impl + FuzzStrategy + for ( + BoxedStrategy<((Arguments, WitnessValues), T)>, + Box>, + ) +{ + fn get_strategy(self, test_context: FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues, FinalTransaction)> { + let (init_strategy, pst_strategy) = self; + + let result_strategy = init_strategy.prop_map(move |((args, wit), additional_value)| { + pst_strategy.gen_final_transaction(test_context.clone(), args, wit, additional_value) + }); + + result_strategy.boxed() + } +} + +pub trait UserFuzzStrategy: Debug { + fn gen_final_transaction( + &self, + test_context: FuzzContext, + arguments: Arguments, + witness: WitnessValues, + ) -> (Arguments, WitnessValues, FinalTransaction); +} + +pub trait UserFuzzStrategyExt: Debug { + fn gen_final_transaction( &self, test_context: FuzzContext, - ) -> BoxedStrategy<(Arguments, WitnessValues, PartiallySignedTransaction)>; + arguments: Arguments, + witness: WitnessValues, + additional_value: T, + ) -> (Arguments, WitnessValues, FinalTransaction); } pub trait ProgramCheck { @@ -44,6 +107,7 @@ pub trait ProgramCheck { tx: &PartiallySignedTransaction, arguments: &Arguments, witness: &WitnessValues, + input_index: usize, program_exec_result: ProgramExecResult, ) -> Result<(), String>; } diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index 3c828943..e8ce768c 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -1,21 +1,27 @@ -use crate::mutantesting::core::{FuzzContext, FuzzStrategy, FuzzableProgram, ProgramCheck, ProgramExecResult}; +use crate::mutantesting::core::{ + FuzzContext, FuzzStrategy, FuzzableProgram, GenStrategy, GenStrategyExt, ProgramCheck, ProgramExecResult, + UserFuzzStrategy, UserFuzzStrategyExt, +}; +use crate::mutantesting::sign_or_extract; use proptest::prelude::TestCaseError; -use simplicityhl::Arguments; -use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, WitnessTrait}; +use proptest::strategy::Strategy; +use simplicityhl::{Arguments, WitnessValues}; +use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; use smplx_sdk::provider::{EsploraProvider, SimplicityNetwork}; use smplx_sdk::signer::Signer; use std::cell::RefCell; use std::marker::PhantomData; use std::sync::Arc; -pub struct SimplexFuzzEngineInner { +pub struct SimplexFuzzEngineInner { pub(crate) fuzz_context: FuzzContext, - pub(crate) strategy_storage: Option>>, + pub(crate) strategy_storage: Option>, + pub(crate) strategy_storage_ext: Option>, } -pub struct SimplexFuzzEngine { +pub struct SimplexFuzzEngine { runner: RefCell, - inner: RefCell>, + inner: RefCell>, _phantom: PhantomData, } @@ -41,11 +47,12 @@ fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { EsploraProvider::new("default_web_page.com".into(), default_network) } -impl SimplexFuzzEngine +impl SimplexFuzzEngine where - Args: ArgumentsTrait + Into + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + std::fmt::Debug + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + Into + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, Program: FuzzableProgram + Clone + 'static, + T: std::fmt::Debug + 'static, { pub fn from_config(mut config: proptest::test_runner::Config, _phantom: PhantomData) -> Self { config.cases = 500; @@ -54,6 +61,7 @@ where inner: RefCell::new(SimplexFuzzEngineInner { fuzz_context: FuzzContext::default(), strategy_storage: None, + strategy_storage_ext: None, }), _phantom, } @@ -73,34 +81,60 @@ where )); } - pub fn with_arg_gen_strategy(&self) - where - S: FuzzStrategy + Default + 'static, - { - self.inner.borrow_mut().strategy_storage = Some(Box::new(S::default())); + pub fn with_final_arg_gen_strategy( + &self, + arg_gen: impl Strategy + 'static, + ft_gen: impl UserFuzzStrategy + 'static, + ) { + self.inner.borrow_mut().strategy_storage = Some((arg_gen.boxed(), Box::new(ft_gen))); + } + + pub fn with_final_arg_gen_strategy_ext( + &self, + arg_gen: impl Strategy + 'static, + ft_gen: impl UserFuzzStrategyExt + 'static, + ) { + self.inner.borrow_mut().strategy_storage_ext = Some((arg_gen.boxed(), Box::new(ft_gen))); } pub fn run_with_check(self, program_check_fn: impl ProgramCheck) { - let mut runner = self.runner.borrow_mut(); - let inner = self.inner.borrow(); + let mut runner = self.runner.into_inner(); + let inner = self.inner.into_inner(); - let strategy_gen = inner.strategy_storage.as_ref().expect("Strategy must be configured"); - let context = inner.fuzz_context.clone(); - let strategy = strategy_gen.get_strategy(context.clone()); + let context = inner.fuzz_context; - match runner.run(&strategy, |(args, wit, pst)| { - let (failure_program, _script) = Program::build_program(args.clone(), &context.network); + let strategy = if let Some(_) = inner.strategy_storage + && let Some(_) = inner.strategy_storage_ext + { + panic!("Strategy must be only one"); + } else if let Some(strategy_gen) = inner.strategy_storage { + strategy_gen.get_strategy(context.clone()) + } else if let Some(strategy_gen_ext) = inner.strategy_storage_ext { + strategy_gen_ext.get_strategy(context.clone()) + } else { + panic!("Strategy must be configured"); + }; + + match runner.run(&strategy, |(args, wit, ft)| { + let signer = context.signer.as_ref(); + let pst = sign_or_extract(signer, &ft).unwrap(); - let exec_result: ProgramExecResult = - failure_program - .as_ref() - .as_ref() - .execute(&pst, &wit, 0, &context.network); + let (failure_program, _script) = Program::build_program(args.clone(), &context.network); - match program_check_fn.call(&context, &pst, &args, &wit, exec_result) { - Ok(_) => Ok(()), - Err(e) => Err(TestCaseError::fail(e)), + // Iterate over program inputs to sign only appropriate items + for (i, input) in ft.inputs().iter().enumerate() { + if input.program_input.as_ref().is_some() { + let exec_result: ProgramExecResult = + failure_program + .as_ref() + .as_ref() + .execute(&pst, &wit, i, &context.network); + if let Err(e) = program_check_fn.call(&context, &pst, &args, &wit, i, exec_result) { + return Err(TestCaseError::fail(e)); + } + } } + Ok(()) }) { Ok(()) => (), Err(e) => ::core::panic!("{}\n{}", e, runner), diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 355561aa..6d01158c 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -1,17 +1,17 @@ mod failure_test_prop { use simplex::mutantesting; - use simplex::mutantesting::core::FuzzStrategy; - use simplex::mutantesting::proptest::prelude::Strategy; + use simplex::mutantesting::core::UserFuzzStrategy; + use simplex::mutantesting::strategy::args::{Random, RandomValuePool}; use simplex::mutantesting::{ - FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, + FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, }; use simplex::simplicityhl::elements::hashes::Hash; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; - use simplex_fixtures::artifacts::failure_test::FailureTestProgram; use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; + use simplex_fixtures::artifacts::failure_test::FailureTestProgram; use std::marker::PhantomData; struct FailureTestCheck; @@ -23,6 +23,7 @@ mod failure_test_prop { _tx: &PartiallySignedTransaction, _arguments: &Arguments, _witness: &WitnessValues, + _input_index: usize, program_exec_result: ProgramExecResult, ) -> Result<(), String> { let args = FailureTestArguments::from_arguments(_arguments)?; @@ -40,114 +41,38 @@ mod failure_test_prop { #[derive(Debug, Default)] struct FailureGenStrategy; - impl FuzzStrategy for FailureGenStrategy { - fn get_strategy( - &self, - test_context: FuzzContext, - ) -> simplex::mutantesting::proptest::strategy::BoxedStrategy<( - Arguments, - WitnessValues, - PartiallySignedTransaction, - )> { - let init_strategy = (simplex::mutantesting::strategy::args::Random::< - FailureTestArguments, - FailureTestWitness, - >::default(),); - - let flat_strategy = init_strategy.prop_flat_map(move |args| { - ( - simplex::mutantesting::proptest::strategy::Just(args.0.0), - simplex::mutantesting::proptest::strategy::Just(args.0.1), - ) - }); - - let result_strategy = flat_strategy.prop_map(move |(args, wit)| { - const DEFAULT_FAUCET: u64 = 1 << 32; - - let mut ft = FinalTransaction::new(); - let (mutated_args, mutated_wit) = (args.clone(), wit.clone()); - - let (failure_program, failure_script) = FailureTestProgram::build_program(args, &test_context.network); - - let txout = { - let mut r = TxOut::new_fee(DEFAULT_FAUCET, test_context.network.policy_asset()); - r.script_pubkey = failure_script; - r - }; - - ft.add_program_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::all_zeros(), 0), - txout, - secrets: None, - }), - ProgramInput::new(Box::new(failure_program.as_ref().as_ref().clone()), wit), - RequiredSignature::None, - ); - - let signer = test_context.signer.as_ref(); - let pst = sign_or_extract(signer, &ft).unwrap(); - - (mutated_args, mutated_wit, pst) - }); - - result_strategy.boxed() - } - } - #[derive(Debug, Default)] - struct FailureGenStrategyWithRandomPool; - impl FuzzStrategy for FailureGenStrategyWithRandomPool { - fn get_strategy( + impl UserFuzzStrategy for FailureGenStrategy { + fn gen_final_transaction( &self, test_context: FuzzContext, - ) -> simplex::mutantesting::proptest::strategy::BoxedStrategy<( - Arguments, - WitnessValues, - PartiallySignedTransaction, - )> { - let init_strategy = (simplex::mutantesting::strategy::args::RandomValuePool::< - FailureTestArguments, - FailureTestWitness, - >::default(),); - - let flat_strategy = init_strategy.prop_flat_map(move |args| { - ( - simplex::mutantesting::proptest::strategy::Just(args.0.0), - simplex::mutantesting::proptest::strategy::Just(args.0.1), - ) - }); - - let result_strategy = flat_strategy.prop_map(move |(args, wit)| { - const DEFAULT_FAUCET: u64 = 1 << 32; - - let mut ft = FinalTransaction::new(); - let (mutated_args, mutated_wit) = (args.clone(), wit.clone()); - - let (failure_program, failure_script) = FailureTestProgram::build_program(args, &test_context.network); - - let txout = { - let mut r = TxOut::new_fee(DEFAULT_FAUCET, test_context.network.policy_asset()); - r.script_pubkey = failure_script; - r - }; - - ft.add_program_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::all_zeros(), 0), - txout, - secrets: None, - }), - ProgramInput::new(Box::new(failure_program.as_ref().as_ref().clone()), wit), - RequiredSignature::None, - ); - - let pst = ft.extract_pst().0; - - (mutated_args, mutated_wit, pst) - }); + arguments: Arguments, + witness: WitnessValues, + ) -> (Arguments, WitnessValues, FinalTransaction) { + const DEFAULT_FAUCET: u64 = 1 << 32; + + let mut ft = FinalTransaction::new(); + let (mutated_args, mutated_wit) = (arguments.clone(), witness.clone()); + + let (failure_program, failure_script) = FailureTestProgram::build_program(arguments, &test_context.network); + + let txout = { + let mut r = TxOut::new_fee(DEFAULT_FAUCET, test_context.network.policy_asset()); + r.script_pubkey = failure_script; + r + }; + + ft.add_program_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::all_zeros(), 0), + txout, + secrets: None, + }), + ProgramInput::new(Box::new(failure_program.as_ref().as_ref().clone()), witness), + RequiredSignature::None, + ); - result_strategy.boxed() + (mutated_args, mutated_wit, ft) } } @@ -175,8 +100,10 @@ mod failure_test_prop { PhantomData, ); - fuzz_engine.with_default_signer(); - fuzz_engine.with_arg_gen_strategy::(); + fuzz_engine.with_final_arg_gen_strategy( + Random::::default(), + FailureGenStrategy::default(), + ); fuzz_engine.run_with_check(FailureTestCheck); @@ -207,8 +134,10 @@ mod failure_test_prop { PhantomData, ); - fuzz_engine.with_default_signer(); - fuzz_engine.with_arg_gen_strategy::(); + fuzz_engine.with_final_arg_gen_strategy( + RandomValuePool::::default(), + FailureGenStrategy::default(), + ); fuzz_engine.run_with_check(FailureTestCheck); @@ -218,24 +147,23 @@ mod failure_test_prop { mod simple_storage_test_prop { use simplex::mutantesting; - use simplex::mutantesting::core::FuzzStrategy; - use simplex::mutantesting::proptest::prelude::Strategy; + use simplex::mutantesting::core::UserFuzzStrategyExt; use simplex::mutantesting::{ - FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, + sign_or_extract, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, }; use simplex::program::{ArgumentsTrait, WitnessTrait}; - use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::hashes::Hash; - use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::pset::serialize::Serialize; + use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; + use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; - use simplex::transaction::PartialOutput; use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; - use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; + use simplex::transaction::PartialOutput; use simplex_fixtures::artifacts::simple_storage::derived_simple_storage::{ SimpleStorageArguments, SimpleStorageWitness, }; + use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; use std::marker::PhantomData; pub struct SimpleStorageCheck; @@ -247,6 +175,7 @@ mod simple_storage_test_prop { _tx: &PartiallySignedTransaction, _arguments: &Arguments, _witness: &WitnessValues, + _input_index: usize, program_exec_result: ProgramExecResult, ) -> Result<(), String> { if let Err(x) = program_exec_result { @@ -260,105 +189,89 @@ mod simple_storage_test_prop { #[derive(Debug, Default)] struct SimpleStorageStrategy; - impl FuzzStrategy for SimpleStorageStrategy { - fn get_strategy( + impl UserFuzzStrategyExt + for SimpleStorageStrategy + { + fn gen_final_transaction( &self, test_context: FuzzContext, - ) -> simplex::mutantesting::proptest::strategy::BoxedStrategy<( - Arguments, - WitnessValues, - PartiallySignedTransaction, - )> { - let init_strategy = ( - simplex::mutantesting::strategy::args::Random::::default( - ), - 0..(u32::MAX as u64), + arguments: Arguments, + witness: WitnessValues, + additional_value: (u64, u64), + ) -> (Arguments, WitnessValues, FinalTransaction) { + let mut ft = FinalTransaction::new(); + let mut args_typed = SimpleStorageArguments::from_arguments(&arguments).unwrap(); + let mut wit_typed = SimpleStorageWitness::from_witness(&witness).unwrap(); + let signer = test_context.signer.as_ref(); + let (new_value, old_value) = additional_value; + + wit_typed.new_value = new_value; + + { + let mut slot: [u8; 32] = Default::default(); + slot.copy_from_slice(&test_context.network.policy_asset().serialize()); + args_typed.slot_id = slot; + args_typed.user = signer.as_ref().unwrap().get_schnorr_public_key().serialize(); + } + let modified_args = args_typed.build_arguments(); + let (_fuzz_program, old_storage_args_script) = + SimpleStorageProgram::build_program(modified_args.clone(), &test_context.network); + + ft.add_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::from_slice(&[1; 32]).unwrap(), 0), + txout: { + let mut r = TxOut::new_fee(old_value, test_context.network.policy_asset()); + r.script_pubkey = old_storage_args_script.clone(); + r + }, + secrets: None, + }), + RequiredSignature::None, ); - let flat_strategy = init_strategy.prop_flat_map(move |((args, wit), old_value)| { - ( - simplex::mutantesting::proptest::strategy::Just(args), - simplex::mutantesting::proptest::strategy::Just(wit), - simplex::mutantesting::proptest::strategy::Just(old_value), - old_value..(u32::MAX as u64), - ) - }); + ft.add_input( + PartialInput::new(UTXO { + outpoint: OutPoint::new(Txid::from_slice(&[2; 32]).unwrap(), 1), + txout: { + let mut r = TxOut::new_fee(1, AssetId::default()); + r.script_pubkey = old_storage_args_script.clone(); + r + }, + secrets: None, + }), + RequiredSignature::None, + ); - let result_strategy = flat_strategy.prop_map(move |(args, wit, old_value, new_value)| { - let mut ft = FinalTransaction::new(); - let mut args_typed = SimpleStorageArguments::from_arguments(&args).unwrap(); - let mut wit_typed = SimpleStorageWitness::from_witness(&wit).unwrap(); - let signer = test_context.signer.as_ref(); - - wit_typed.new_value = new_value; - - { - let mut slot: [u8; 32] = Default::default(); - slot.copy_from_slice(&test_context.network.policy_asset().serialize()); - args_typed.slot_id = slot; - args_typed.user = signer.as_ref().unwrap().get_schnorr_public_key().serialize(); - } - let modified_args = args_typed.build_arguments(); - let (_fuzz_program, old_storage_args_script) = - SimpleStorageProgram::build_program(modified_args.clone(), &test_context.network); - - ft.add_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::from_slice(&[1; 32]).unwrap(), 0), - txout: { - let mut r = TxOut::new_fee(old_value, test_context.network.policy_asset()); - r.script_pubkey = old_storage_args_script.clone(); - r - }, - secrets: None, - }), - RequiredSignature::None, - ); - - ft.add_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::from_slice(&[2; 32]).unwrap(), 1), - txout: { - let mut r = TxOut::new_fee(1, AssetId::default()); - r.script_pubkey = old_storage_args_script.clone(); - r - }, - secrets: None, - }), - RequiredSignature::None, - ); - - ft.add_output(PartialOutput { - script_pubkey: old_storage_args_script.clone(), - amount: new_value, - asset: test_context.network.policy_asset(), - blinding_key: None, - }); - ft.add_output(PartialOutput { - script_pubkey: old_storage_args_script, - amount: 0, - asset: Default::default(), - blinding_key: None, - }); - - let pst = sign_or_extract(signer, &ft).unwrap(); - let wit_signed = signer - .as_ref() - .unwrap() - .get_signed_program_witness( - &pst, - SimpleStorageProgram::new(modified_args.clone()).as_ref(), - &wit_typed.build_witness(), - "USER_SIGNATURE", - &[], - 0, - ) - .unwrap(); - - (modified_args, wit_signed, pst) + ft.add_output(PartialOutput { + script_pubkey: old_storage_args_script.clone(), + amount: new_value, + asset: test_context.network.policy_asset(), + blinding_key: None, + }); + ft.add_output(PartialOutput { + script_pubkey: old_storage_args_script, + amount: 0, + asset: Default::default(), + blinding_key: None, }); - result_strategy.boxed() + // TODO: how to make correctly here? + let pst = sign_or_extract(signer, &ft).unwrap(); + let wit_signed = signer + .as_ref() + .unwrap() + .get_signed_program_witness( + &pst, + SimpleStorageProgram::new(modified_args.clone()).as_ref(), + &wit_typed.build_witness(), + "USER_SIGNATURE", + &[], + 0, + ) + .unwrap(); + + (modified_args, wit_signed, ft) } } @@ -380,14 +293,24 @@ mod simple_storage_test_prop { ..Default::default() }; - let fuzz_engine = - SimplexFuzzEngine::::from_config( - config, - PhantomData, - ); + let fuzz_engine = SimplexFuzzEngine::< + SimpleStorageProgram, + SimpleStorageArguments, + SimpleStorageWitness, + (u64, u64), + >::from_config(config, PhantomData); fuzz_engine.with_default_signer(); - fuzz_engine.with_arg_gen_strategy::(); + fuzz_engine.with_final_arg_gen_strategy_ext( + ( + simplex::mutantesting::strategy::args::Random::::default(), + ( + 0_u64..((u32::MAX / 2) as u64), + ((u32::MAX / 2) as u64)..(u32::MAX as u64), + ), + ), + SimpleStorageStrategy::default(), + ); fuzz_engine.run_with_check(SimpleStorageCheck); From 79c89c90cf65e99e690a580cc14ee26adbb58751 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Thu, 18 Jun 2026 17:18:26 +0300 Subject: [PATCH 13/21] proptest: simplify implementation, remove some explicit generics definition --- crates/test/src/mutantesting/core.rs | 65 ++-------------------- crates/test/src/mutantesting/engine.rs | 74 ++++++++++++-------------- fixtures/tests/prop_testing.rs | 57 ++++++++------------ 3 files changed, 62 insertions(+), 134 deletions(-) diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index f0a7407c..02960d95 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -1,15 +1,13 @@ -use proptest::prelude::{BoxedStrategy, Strategy}; use simplicityhl::elements::Script; use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::simplicity::{RedeemNode, Value}; use simplicityhl::{Arguments, WitnessValues}; -use smplx_sdk::program::{Program, ProgramError, ProgramFactory, RandomArguments, RandomWitness}; +use smplx_sdk::program::{Program, ProgramError, ProgramFactory}; use smplx_sdk::provider::{ProviderTrait, SimplicityNetwork}; use smplx_sdk::signer::Signer; use smplx_sdk::transaction::FinalTransaction; -use std::fmt::Debug; use std::sync::Arc; #[derive(Clone)] @@ -21,16 +19,6 @@ pub struct FuzzContext { pub type ProgramExecResult = Result<(Arc, Value), ProgramError>; -pub type GenStrategyExt = ( - BoxedStrategy<((Arguments, WitnessValues), T)>, - Box>, -); - -pub type GenStrategy = ( - BoxedStrategy<(Arguments, WitnessValues)>, - Box>, -); - pub trait FuzzableProgram>: AsRef { fn build_program(args: impl Into, network: &SimplicityNetwork) -> (Box

, Script); } @@ -43,60 +31,15 @@ impl + ProgramFactory

> FuzzableProgram

for P { } } -pub trait FuzzStrategy: Debug { - fn get_strategy(self, test_context: FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues, FinalTransaction)>; -} - -impl FuzzStrategy - for ( - BoxedStrategy<(Arguments, WitnessValues)>, - Box>, - ) -{ - fn get_strategy(self, test_context: FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues, FinalTransaction)> { - let (init_strategy, pst_strategy) = self; - - let result_strategy = init_strategy - .prop_map(move |(args, wit)| pst_strategy.gen_final_transaction(test_context.clone(), args, wit)); - - result_strategy.boxed() - } -} - -impl - FuzzStrategy - for ( - BoxedStrategy<((Arguments, WitnessValues), T)>, - Box>, - ) -{ - fn get_strategy(self, test_context: FuzzContext) -> BoxedStrategy<(Arguments, WitnessValues, FinalTransaction)> { - let (init_strategy, pst_strategy) = self; - - let result_strategy = init_strategy.prop_map(move |((args, wit), additional_value)| { - pst_strategy.gen_final_transaction(test_context.clone(), args, wit, additional_value) - }); - - result_strategy.boxed() - } -} - -pub trait UserFuzzStrategy: Debug { - fn gen_final_transaction( - &self, - test_context: FuzzContext, - arguments: Arguments, - witness: WitnessValues, - ) -> (Arguments, WitnessValues, FinalTransaction); -} +pub trait ContractFuzzStrategy: std::fmt::Debug { + type AdditionalInput: std::fmt::Debug + 'static; -pub trait UserFuzzStrategyExt: Debug { fn gen_final_transaction( &self, test_context: FuzzContext, arguments: Arguments, witness: WitnessValues, - additional_value: T, + additional: Self::AdditionalInput, ) -> (Arguments, WitnessValues, FinalTransaction); } diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index e8ce768c..3ccf6892 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -1,28 +1,25 @@ -use crate::mutantesting::core::{ - FuzzContext, FuzzStrategy, FuzzableProgram, GenStrategy, GenStrategyExt, ProgramCheck, ProgramExecResult, - UserFuzzStrategy, UserFuzzStrategyExt, -}; +use crate::mutantesting::core::{ContractFuzzStrategy, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; use crate::mutantesting::sign_or_extract; -use proptest::prelude::TestCaseError; +use proptest::prelude::{BoxedStrategy, TestCaseError}; use proptest::strategy::Strategy; use simplicityhl::{Arguments, WitnessValues}; use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; use smplx_sdk::provider::{EsploraProvider, SimplicityNetwork}; use smplx_sdk::signer::Signer; +use smplx_sdk::transaction::FinalTransaction; use std::cell::RefCell; use std::marker::PhantomData; use std::sync::Arc; -pub struct SimplexFuzzEngineInner { +pub struct SimplexFuzzEngineInner { pub(crate) fuzz_context: FuzzContext, - pub(crate) strategy_storage: Option>, - pub(crate) strategy_storage_ext: Option>, + pub(crate) strategy_storage: Option>, + _phantom: PhantomData, } -pub struct SimplexFuzzEngine { +pub struct SimplexFuzzEngine { runner: RefCell, - inner: RefCell>, - _phantom: PhantomData, + inner: RefCell>, } #[allow(clippy::arc_with_non_send_sync)] @@ -47,23 +44,19 @@ fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { EsploraProvider::new("default_web_page.com".into(), default_network) } -impl SimplexFuzzEngine +impl SimplexFuzzEngine where - Args: ArgumentsTrait + RandomArguments + Into + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, Program: FuzzableProgram + Clone + 'static, - T: std::fmt::Debug + 'static, { - pub fn from_config(mut config: proptest::test_runner::Config, _phantom: PhantomData) -> Self { + pub fn from_config(mut config: proptest::test_runner::Config) -> Self { config.cases = 500; Self { runner: RefCell::new(proptest::test_runner::TestRunner::new(config)), inner: RefCell::new(SimplexFuzzEngineInner { fuzz_context: FuzzContext::default(), strategy_storage: None, - strategy_storage_ext: None, + _phantom: PhantomData, }), - _phantom, } } @@ -81,20 +74,34 @@ where )); } - pub fn with_final_arg_gen_strategy( + pub fn with_final_arg_gen_strategy( &self, arg_gen: impl Strategy + 'static, - ft_gen: impl UserFuzzStrategy + 'static, - ) { - self.inner.borrow_mut().strategy_storage = Some((arg_gen.boxed(), Box::new(ft_gen))); + ft_gen: S, + ) where + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + S: ContractFuzzStrategy + 'static, + { + let context = self.inner.borrow().fuzz_context.clone(); + let mapped = arg_gen.prop_map(move |(args, wit)| ft_gen.gen_final_transaction(context.clone(), args, wit, ())); + self.inner.borrow_mut().strategy_storage = Some(mapped.boxed()); } - pub fn with_final_arg_gen_strategy_ext( + pub fn with_final_arg_gen_strategy_ext( &self, - arg_gen: impl Strategy + 'static, - ft_gen: impl UserFuzzStrategyExt + 'static, - ) { - self.inner.borrow_mut().strategy_storage_ext = Some((arg_gen.boxed(), Box::new(ft_gen))); + arg_gen: impl Strategy + 'static, + ft_gen: S, + ) where + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + S: ContractFuzzStrategy + 'static, + { + let context = self.inner.borrow().fuzz_context.clone(); + let mapped = arg_gen.prop_map(move |((args, wit), additional)| { + ft_gen.gen_final_transaction(context.clone(), args, wit, additional) + }); + self.inner.borrow_mut().strategy_storage = Some(mapped.boxed()); } pub fn run_with_check(self, program_check_fn: impl ProgramCheck) { @@ -102,18 +109,7 @@ where let inner = self.inner.into_inner(); let context = inner.fuzz_context; - - let strategy = if let Some(_) = inner.strategy_storage - && let Some(_) = inner.strategy_storage_ext - { - panic!("Strategy must be only one"); - } else if let Some(strategy_gen) = inner.strategy_storage { - strategy_gen.get_strategy(context.clone()) - } else if let Some(strategy_gen_ext) = inner.strategy_storage_ext { - strategy_gen_ext.get_strategy(context.clone()) - } else { - panic!("Strategy must be configured"); - }; + let strategy = inner.strategy_storage.expect("Strategy must be configured"); match runner.run(&strategy, |(args, wit, ft)| { let signer = context.signer.as_ref(); diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 6d01158c..63b700b4 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -1,18 +1,15 @@ mod failure_test_prop { use simplex::mutantesting; - use simplex::mutantesting::core::UserFuzzStrategy; + use simplex::mutantesting::core::ContractFuzzStrategy; use simplex::mutantesting::strategy::args::{Random, RandomValuePool}; - use simplex::mutantesting::{ - FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, - }; + use simplex::mutantesting::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine}; use simplex::simplicityhl::elements::hashes::Hash; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; - use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; use simplex_fixtures::artifacts::failure_test::FailureTestProgram; - use std::marker::PhantomData; + use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; struct FailureTestCheck; @@ -42,12 +39,15 @@ mod failure_test_prop { #[derive(Debug, Default)] struct FailureGenStrategy; - impl UserFuzzStrategy for FailureGenStrategy { + impl ContractFuzzStrategy for FailureGenStrategy { + type AdditionalInput = (); + fn gen_final_transaction( &self, test_context: FuzzContext, arguments: Arguments, witness: WitnessValues, + _additional: Self::AdditionalInput, ) -> (Arguments, WitnessValues, FinalTransaction) { const DEFAULT_FAUCET: u64 = 1 << 32; @@ -94,11 +94,7 @@ mod failure_test_prop { ..Default::default() }; - let fuzz_engine = - SimplexFuzzEngine::::from_config( - config, - PhantomData, - ); + let fuzz_engine = SimplexFuzzEngine::::from_config(config); fuzz_engine.with_final_arg_gen_strategy( Random::::default(), @@ -128,11 +124,7 @@ mod failure_test_prop { ..Default::default() }; - let fuzz_engine = - SimplexFuzzEngine::::from_config( - config, - PhantomData, - ); + let fuzz_engine = SimplexFuzzEngine::::from_config(config); fuzz_engine.with_final_arg_gen_strategy( RandomValuePool::::default(), @@ -147,24 +139,23 @@ mod failure_test_prop { mod simple_storage_test_prop { use simplex::mutantesting; - use simplex::mutantesting::core::UserFuzzStrategyExt; + use simplex::mutantesting::core::ContractFuzzStrategy; use simplex::mutantesting::{ - sign_or_extract, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, + FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, }; use simplex::program::{ArgumentsTrait, WitnessTrait}; + use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::hashes::Hash; - use simplex::simplicityhl::elements::pset::serialize::Serialize; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; - use simplex::simplicityhl::elements::AssetId; + use simplex::simplicityhl::elements::pset::serialize::Serialize; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; - use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; use simplex::transaction::PartialOutput; + use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; + use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; use simplex_fixtures::artifacts::simple_storage::derived_simple_storage::{ SimpleStorageArguments, SimpleStorageWitness, }; - use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; - use std::marker::PhantomData; pub struct SimpleStorageCheck; @@ -189,21 +180,23 @@ mod simple_storage_test_prop { #[derive(Debug, Default)] struct SimpleStorageStrategy; - impl UserFuzzStrategyExt + impl ContractFuzzStrategy for SimpleStorageStrategy { + type AdditionalInput = (u64, u64); + fn gen_final_transaction( &self, test_context: FuzzContext, arguments: Arguments, witness: WitnessValues, - additional_value: (u64, u64), + additional: Self::AdditionalInput, ) -> (Arguments, WitnessValues, FinalTransaction) { let mut ft = FinalTransaction::new(); let mut args_typed = SimpleStorageArguments::from_arguments(&arguments).unwrap(); let mut wit_typed = SimpleStorageWitness::from_witness(&witness).unwrap(); let signer = test_context.signer.as_ref(); - let (new_value, old_value) = additional_value; + let (new_value, old_value) = additional; wit_typed.new_value = new_value; @@ -293,17 +286,13 @@ mod simple_storage_test_prop { ..Default::default() }; - let fuzz_engine = SimplexFuzzEngine::< - SimpleStorageProgram, - SimpleStorageArguments, - SimpleStorageWitness, - (u64, u64), - >::from_config(config, PhantomData); + let fuzz_engine = SimplexFuzzEngine::::from_config(config); fuzz_engine.with_default_signer(); fuzz_engine.with_final_arg_gen_strategy_ext( ( - simplex::mutantesting::strategy::args::Random::::default(), + simplex::mutantesting::strategy::args::Random::::default( + ), ( 0_u64..((u32::MAX / 2) as u64), ((u32::MAX / 2) as u64)..(u32::MAX as u64), From 2c4504ef0a71c9b393389518d3c5535eb5c65413 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Thu, 18 Jun 2026 17:45:12 +0300 Subject: [PATCH 14/21] build: fix typo in AND operation --- crates/build/src/macros/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/build/src/macros/types.rs b/crates/build/src/macros/types.rs index fd44bc67..8bca1f8e 100644 --- a/crates/build/src/macros/types.rs +++ b/crates/build/src/macros/types.rs @@ -64,8 +64,8 @@ impl RustType { match self { RustType::Bool => quote! { rng.random() }, RustType::U1 => quote! { rng.random::() as u8 }, - RustType::U2 => quote! { rng.random::() & 0x00_03 }, - RustType::U4 => quote! { rng.random::() & 0x00_0F }, + RustType::U2 => quote! { rng.random::() & 0x03 }, + RustType::U4 => quote! { rng.random::() & 0x0F }, RustType::U8 | RustType::U16 | RustType::U32 | RustType::U64 | RustType::U128 => quote! { rng.random() }, RustType::U256Array => quote! { rng.random() }, RustType::Array(element, size) => { From b1f1314226e2cbb49082a1f88506a3a457f562d4 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Thu, 18 Jun 2026 17:56:51 +0300 Subject: [PATCH 15/21] deps: sort deps in edited files --- crates/test/src/lib.rs | 1 - .../{strategy/args.rs => args_strategy.rs} | 8 +++++--- crates/test/src/mutantesting/core.rs | 4 ++-- crates/test/src/mutantesting/engine.rs | 14 +++++++++----- crates/test/src/mutantesting/mod.rs | 2 +- crates/test/src/mutantesting/strategy.rs | 1 - crates/test/src/mutantesting/utils.rs | 2 ++ fixtures/tests/prop_testing.rs | 16 ++++++++-------- 8 files changed, 27 insertions(+), 21 deletions(-) rename crates/test/src/mutantesting/{strategy/args.rs => args_strategy.rs} (99%) delete mode 100644 crates/test/src/mutantesting/strategy.rs diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 14109bfa..c68625ab 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -2,7 +2,6 @@ pub mod config; pub mod context; pub mod error; pub mod macros; -/// General utilities and traits for implementing property based testing in Simplicity contracts. pub mod mutantesting; pub mod network_utils; diff --git a/crates/test/src/mutantesting/strategy/args.rs b/crates/test/src/mutantesting/args_strategy.rs similarity index 99% rename from crates/test/src/mutantesting/strategy/args.rs rename to crates/test/src/mutantesting/args_strategy.rs index d7d0cff7..af80be23 100644 --- a/crates/test/src/mutantesting/strategy/args.rs +++ b/crates/test/src/mutantesting/args_strategy.rs @@ -1,14 +1,16 @@ -use proptest::prelude::Strategy; -use proptest::strategy::{NewTree, ValueTree}; +use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; use proptest::prelude::Rng; +use proptest::prelude::Strategy; +use proptest::strategy::{NewTree, ValueTree}; use proptest::test_runner::{TestRng, TestRunner}; + use simplicityhl::str::WitnessName; use simplicityhl::{Arguments, ResolvedType, Value, WitnessValues}; + use smplx_sdk::program::{RandomArguments, RandomWitness}; -use std::collections::HashMap; pub struct Random { phantom_data: PhantomData<(Args, Wit)>, diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index 02960d95..96d91038 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use simplicityhl::elements::Script; use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::simplicity::{RedeemNode, Value}; @@ -8,8 +10,6 @@ use smplx_sdk::provider::{ProviderTrait, SimplicityNetwork}; use smplx_sdk::signer::Signer; use smplx_sdk::transaction::FinalTransaction; -use std::sync::Arc; - #[derive(Clone)] pub struct FuzzContext { pub signer: Arc>, diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index 3ccf6892..aca055b4 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -1,15 +1,19 @@ -use crate::mutantesting::core::{ContractFuzzStrategy, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; -use crate::mutantesting::sign_or_extract; +use std::cell::RefCell; +use std::marker::PhantomData; +use std::sync::Arc; + use proptest::prelude::{BoxedStrategy, TestCaseError}; use proptest::strategy::Strategy; + use simplicityhl::{Arguments, WitnessValues}; + use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; use smplx_sdk::provider::{EsploraProvider, SimplicityNetwork}; use smplx_sdk::signer::Signer; use smplx_sdk::transaction::FinalTransaction; -use std::cell::RefCell; -use std::marker::PhantomData; -use std::sync::Arc; + +use crate::mutantesting::core::{ContractFuzzStrategy, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; +use crate::mutantesting::sign_or_extract; pub struct SimplexFuzzEngineInner { pub(crate) fuzz_context: FuzzContext, diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index 044ef1a2..f00db892 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -1,6 +1,6 @@ +pub mod args_strategy; pub mod core; pub mod engine; -pub mod strategy; pub mod utils; pub use proptest; diff --git a/crates/test/src/mutantesting/strategy.rs b/crates/test/src/mutantesting/strategy.rs deleted file mode 100644 index 6e10f4ad..00000000 --- a/crates/test/src/mutantesting/strategy.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod args; diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/mutantesting/utils.rs index d3bfabe1..adce2c82 100644 --- a/crates/test/src/mutantesting/utils.rs +++ b/crates/test/src/mutantesting/utils.rs @@ -1,9 +1,11 @@ use rand::Rng; + use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::num::U256; use simplicityhl::types::{TypeInner, UIntType}; use simplicityhl::value::ValueConstructible; use simplicityhl::{ResolvedType, Value}; + use smplx_sdk::signer::{Signer, SignerError}; use smplx_sdk::transaction::FinalTransaction; diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 63b700b4..7fe90b8a 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -1,15 +1,15 @@ mod failure_test_prop { use simplex::mutantesting; + use simplex::mutantesting::args_strategy::{Random, RandomValuePool}; use simplex::mutantesting::core::ContractFuzzStrategy; - use simplex::mutantesting::strategy::args::{Random, RandomValuePool}; use simplex::mutantesting::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine}; use simplex::simplicityhl::elements::hashes::Hash; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; - use simplex_fixtures::artifacts::failure_test::FailureTestProgram; use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; + use simplex_fixtures::artifacts::failure_test::FailureTestProgram; struct FailureTestCheck; @@ -139,23 +139,24 @@ mod failure_test_prop { mod simple_storage_test_prop { use simplex::mutantesting; + use simplex::mutantesting::args_strategy::Random; use simplex::mutantesting::core::ContractFuzzStrategy; use simplex::mutantesting::{ - FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, sign_or_extract, + sign_or_extract, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, }; use simplex::program::{ArgumentsTrait, WitnessTrait}; - use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::hashes::Hash; - use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::pset::serialize::Serialize; + use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; + use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; use simplex::transaction::PartialOutput; use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; - use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; use simplex_fixtures::artifacts::simple_storage::derived_simple_storage::{ SimpleStorageArguments, SimpleStorageWitness, }; + use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; pub struct SimpleStorageCheck; @@ -291,8 +292,7 @@ mod simple_storage_test_prop { fuzz_engine.with_default_signer(); fuzz_engine.with_final_arg_gen_strategy_ext( ( - simplex::mutantesting::strategy::args::Random::::default( - ), + Random::::default(), ( 0_u64..((u32::MAX / 2) as u64), ((u32::MAX / 2) as u64)..(u32::MAX as u64), From ca8ad3ca12320a9b660a884d1fabafba63155cd9 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Fri, 19 Jun 2026 13:06:38 +0300 Subject: [PATCH 16/21] config: add proptest config for simplex binary --- crates/cli/assets/Simplex.default.toml | 6 ++ crates/test/Cargo.toml | 2 +- crates/test/src/config.rs | 10 +++ crates/test/src/mutantesting/core.rs | 29 ++++++++- crates/test/src/mutantesting/engine.rs | 85 +++++++++++++++++++++----- crates/test/src/mutantesting/mod.rs | 2 +- crates/test/src/mutantesting/utils.rs | 15 ----- examples/basic/Simplex.toml | 6 ++ fixtures/Simplex.toml | 6 ++ fixtures/tests/prop_testing.rs | 6 +- 10 files changed, 131 insertions(+), 36 deletions(-) diff --git a/crates/cli/assets/Simplex.default.toml b/crates/cli/assets/Simplex.default.toml index e8eff361..fdc50134 100644 --- a/crates/cli/assets/Simplex.default.toml +++ b/crates/cli/assets/Simplex.default.toml @@ -22,6 +22,12 @@ # url = "" # network = "" +# [test.proptest] +# cases = 10000 +# max_shrink_iters = 100 +# max_global_rejects = 100 +# max_local_rejects = 100 + # [test.rpc] # url = "" # username = "" diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 39eec76a..3e1d5f03 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -24,5 +24,5 @@ rand = { workspace = true } syn = { version = "2.0.114", default-features = false, features = ["proc-macro", "full", "parsing", "derive", "clone-impls", "extra-traits", "printing"] } proc-macro2 = { version = "1.0.106", features = ["span-locations"] } quote = { version = "1.0.44" } -proptest = { version = "1.11.0" } +proptest = { version = "1.11.0", features = ["std"] } diff --git a/crates/test/src/config.rs b/crates/test/src/config.rs index bacb551d..ca336821 100644 --- a/crates/test/src/config.rs +++ b/crates/test/src/config.rs @@ -22,6 +22,7 @@ pub struct TestConfig { pub bitcoins: u64, pub esplora: Option, pub rpc: Option, + pub proptest: Option, pub verbosity: Verbosity, } @@ -38,6 +39,14 @@ pub struct RpcConfig { pub password: String, } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct ProptestConfig { + pub cases: Option, + pub max_shrink_iters: Option, + pub max_global_rejects: Option, + pub max_local_rejects: Option, +} + impl TestConfig { pub fn from_file(path: impl AsRef) -> Result { let mut content = String::new(); @@ -80,6 +89,7 @@ impl Default for TestConfig { bitcoins: DEFAULT_BITCOINS, esplora: None, rpc: None, + proptest: None, verbosity: Verbosity::None, } } diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index 96d91038..e2fc620f 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -10,10 +10,21 @@ use smplx_sdk::provider::{ProviderTrait, SimplicityNetwork}; use smplx_sdk::signer::Signer; use smplx_sdk::transaction::FinalTransaction; +use crate::context::TestContext; + +#[derive(Clone, Debug)] +pub(crate) enum SignerOption { + DefaultTestConfigSigner, + CustomSigner, + NoSigning, +} + #[derive(Clone)] pub struct FuzzContext { - pub signer: Arc>, + pub(crate) signer: Arc>, pub mock_provider: Arc, + pub(crate) test_context: Arc>, + pub(crate) signer_option: SignerOption, pub network: SimplicityNetwork, } @@ -31,6 +42,22 @@ impl + ProgramFactory

> FuzzableProgram

for P { } } +impl FuzzContext { + pub fn get_signer(&self) -> Option<&Signer> { + match self.signer_option { + SignerOption::DefaultTestConfigSigner => Some( + self.test_context + .as_ref() + .as_ref() + .expect("TestContext has to be unempty") + .get_default_signer(), + ), + SignerOption::CustomSigner => self.signer.as_ref().as_ref(), + SignerOption::NoSigning => None, + } + } +} + pub trait ContractFuzzStrategy: std::fmt::Debug { type AdditionalInput: std::fmt::Debug + 'static; diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index aca055b4..2d900045 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -5,16 +5,17 @@ use std::sync::Arc; use proptest::prelude::{BoxedStrategy, TestCaseError}; use proptest::strategy::Strategy; +use crate::context::TestContext; +use crate::mutantesting::core::{ + ContractFuzzStrategy, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SignerOption, +}; +use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::{Arguments, WitnessValues}; - use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; use smplx_sdk::provider::{EsploraProvider, SimplicityNetwork}; -use smplx_sdk::signer::Signer; +use smplx_sdk::signer::{Signer, SignerError}; use smplx_sdk::transaction::FinalTransaction; -use crate::mutantesting::core::{ContractFuzzStrategy, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; -use crate::mutantesting::sign_or_extract; - pub struct SimplexFuzzEngineInner { pub(crate) fuzz_context: FuzzContext, pub(crate) strategy_storage: Option>, @@ -34,15 +35,34 @@ impl Default for FuzzContext { signer: Arc::new(None), network: default_network, mock_provider: Arc::new(get_default_provider(default_network)), + test_context: Arc::new(None), + signer_option: SignerOption::NoSigning, } } } impl FuzzContext { #[allow(clippy::arc_with_non_send_sync)] - fn with_signer(&mut self, signer: Signer) { + pub fn with_signer(&mut self, signer: Signer) { + self.signer_option = SignerOption::CustomSigner; self.signer = Arc::new(Some(signer)); } + + #[allow(clippy::arc_with_non_send_sync)] + pub fn with_default_signer(&mut self) { + self.signer_option = SignerOption::DefaultTestConfigSigner; + } + + #[inline] + pub fn sign_or_extract(&self, ft: &FinalTransaction) -> Result { + match self.signer_option { + SignerOption::DefaultTestConfigSigner | SignerOption::CustomSigner => { + let signer = self.get_signer(); + Ok(signer.unwrap().sign_tx_raw(ft)?) + } + SignerOption::NoSigning => Ok(ft.extract_pst().0), + } + } } fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { EsploraProvider::new("default_web_page.com".into(), default_network) @@ -64,18 +84,54 @@ where } } + pub fn from_context(mut config: proptest::test_runner::Config, test_context: TestContext) -> Self { + let default_network = SimplicityNetwork::default_regtest(); + let smplx_test_context = dbg!(test_context.get_config()); + if let Some(proptest_conf) = smplx_test_context.proptest.as_ref() { + if let Some(cases) = proptest_conf.cases { + config.cases = cases; + } + + if let Some(max_global_rejects) = proptest_conf.max_global_rejects { + config.max_global_rejects = max_global_rejects; + } + + if let Some(max_shrink_iters) = proptest_conf.max_shrink_iters { + config.max_shrink_iters = max_shrink_iters; + } + + if let Some(max_local_rejects) = proptest_conf.max_local_rejects { + config.max_local_rejects = max_local_rejects; + } + + config.verbose = smplx_test_context.verbosity as u32; + } + + Self { + runner: RefCell::new(proptest::test_runner::TestRunner::new(config)), + inner: RefCell::new(SimplexFuzzEngineInner { + fuzz_context: FuzzContext { + #[allow(clippy::arc_with_non_send_sync)] + signer: Arc::new(None), + #[allow(clippy::arc_with_non_send_sync)] + mock_provider: Arc::new(get_default_provider(default_network)), + #[allow(clippy::arc_with_non_send_sync)] + test_context: Arc::new(Some(test_context)), + signer_option: SignerOption::NoSigning, + network: SimplicityNetwork::Liquid, + }, + strategy_storage: None, + _phantom: PhantomData, + }), + } + } + pub fn with_signer(&self, signer: Signer) { self.inner.borrow_mut().fuzz_context.with_signer(signer); } pub fn with_default_signer(&self) { - const DEFAULT_TEST_MNEMONIC: &str = "exist carry drive collect lend cereal occur much tiger just involve mean"; - - let network = self.inner.borrow().fuzz_context.network; - self.inner.borrow_mut().fuzz_context.with_signer(Signer::new( - DEFAULT_TEST_MNEMONIC, - Box::new(get_default_provider(network)), - )); + self.inner.borrow_mut().fuzz_context.with_default_signer(); } pub fn with_final_arg_gen_strategy( @@ -116,8 +172,7 @@ where let strategy = inner.strategy_storage.expect("Strategy must be configured"); match runner.run(&strategy, |(args, wit, ft)| { - let signer = context.signer.as_ref(); - let pst = sign_or_extract(signer, &ft).unwrap(); + let pst = context.sign_or_extract(&ft).unwrap(); let (failure_program, _script) = Program::build_program(args.clone(), &context.network); diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index f00db892..8256ec91 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -7,4 +7,4 @@ pub use proptest; pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; pub use engine::SimplexFuzzEngine; -pub use utils::{generate_value_by_ty, sign_or_extract}; +pub use utils::generate_value_by_ty; diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/mutantesting/utils.rs index adce2c82..762a693b 100644 --- a/crates/test/src/mutantesting/utils.rs +++ b/crates/test/src/mutantesting/utils.rs @@ -1,14 +1,10 @@ use rand::Rng; -use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::num::U256; use simplicityhl::types::{TypeInner, UIntType}; use simplicityhl::value::ValueConstructible; use simplicityhl::{ResolvedType, Value}; -use smplx_sdk::signer::{Signer, SignerError}; -use smplx_sdk::transaction::FinalTransaction; - pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> Value { match ty.as_inner() { TypeInner::Either(left_ty, right_ty) => match rng.random::() { @@ -47,14 +43,3 @@ pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> _ => Value::unit(), } } - -#[inline] -pub fn sign_or_extract( - signer: &Option, - ft: &FinalTransaction, -) -> Result { - match signer.as_ref() { - None => Ok(ft.extract_pst().0), - Some(signer) => signer.sign_tx_raw(ft), - } -} diff --git a/examples/basic/Simplex.toml b/examples/basic/Simplex.toml index c522f64c..deaeafca 100644 --- a/examples/basic/Simplex.toml +++ b/examples/basic/Simplex.toml @@ -21,6 +21,12 @@ # bitcoins = 10_000_000 # verbosity = 0 +# [test.proptest] +# cases = 10000 +# max_shrink_iters = 100 +# max_global_rejects = 100 +# max_local_rejects = 100 + # [test.esplora] # url = "" # network = "" diff --git a/fixtures/Simplex.toml b/fixtures/Simplex.toml index 041dc506..e5b3467c 100644 --- a/fixtures/Simplex.toml +++ b/fixtures/Simplex.toml @@ -24,6 +24,12 @@ test_crate = { git = "https://github.com/LesterEvSe/test-simplex-crate" } # bitcoins = 10_000_000 # verbosity = 0 +# [test.proptest] +# cases = 10000 +# max_shrink_iters = 100 +# max_global_rejects = 100 +# max_local_rejects = 100 + # [test.esplora] # url = "" # network = "" diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 7fe90b8a..e4b6e679 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -142,7 +142,7 @@ mod simple_storage_test_prop { use simplex::mutantesting::args_strategy::Random; use simplex::mutantesting::core::ContractFuzzStrategy; use simplex::mutantesting::{ - sign_or_extract, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, + FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, }; use simplex::program::{ArgumentsTrait, WitnessTrait}; use simplex::simplicityhl::elements::hashes::Hash; @@ -196,7 +196,7 @@ mod simple_storage_test_prop { let mut ft = FinalTransaction::new(); let mut args_typed = SimpleStorageArguments::from_arguments(&arguments).unwrap(); let mut wit_typed = SimpleStorageWitness::from_witness(&witness).unwrap(); - let signer = test_context.signer.as_ref(); + let signer = test_context.get_signer(); let (new_value, old_value) = additional; wit_typed.new_value = new_value; @@ -251,7 +251,7 @@ mod simple_storage_test_prop { }); // TODO: how to make correctly here? - let pst = sign_or_extract(signer, &ft).unwrap(); + let pst = test_context.sign_or_extract( &ft).unwrap(); let wit_signed = signer .as_ref() .unwrap() From dd38fc2c978a3b879d30237f25c4a4d2e05f9853 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Wed, 24 Jun 2026 13:44:11 +0300 Subject: [PATCH 17/21] proptesting: add another builder structs structure * add a macros implementation for proptesting * add Clone for EsploraProvider * add possibility to run proptests via the cli --- crates/cli/src/commands/core.rs | 3 + crates/cli/src/commands/test.rs | 3 + crates/macros/src/lib.rs | 12 +- crates/sdk/src/provider/esplora.rs | 2 +- crates/simplex/src/lib.rs | 2 +- crates/test/src/macros/core.rs | 64 +++- crates/test/src/macros/mod.rs | 2 +- crates/test/src/mutantesting/core.rs | 27 +- crates/test/src/mutantesting/engine.rs | 461 +++++++++++++++++++------ crates/test/src/mutantesting/mod.rs | 2 +- fixtures/tests/prop_testing.rs | 196 ++++++++--- 11 files changed, 612 insertions(+), 162 deletions(-) diff --git a/crates/cli/src/commands/core.rs b/crates/cli/src/commands/core.rs index 0927774a..0eeb93ae 100644 --- a/crates/cli/src/commands/core.rs +++ b/crates/cli/src/commands/core.rs @@ -62,4 +62,7 @@ pub struct TestFlags { /// Run non-simplex tests (may be used for running unit tests) #[arg(long = "no-simplex")] pub no_simplex: bool, + /// Run proptests via simplex + #[arg(long = "proptest")] + pub proptest: bool, } diff --git a/crates/cli/src/commands/test.rs b/crates/cli/src/commands/test.rs index bb624526..0df60e20 100644 --- a/crates/cli/src/commands/test.rs +++ b/crates/cli/src/commands/test.rs @@ -9,6 +9,7 @@ use super::error::CommandError; /// Nextest dsl variable to filter and use only simplex tests const SMPLX_NEXTEST_DSL_TEST_MARKER: &str = concat!("test(/", smplx_test_marker!(), "$/)"); +const SMPLX_NEXTEST_PROPTEST_DSL_TEST_MARKER: &str = concat!("test(/", smplx_test_marker!(prop), "$/)"); const DEFAULT_THREADS_NUMBER: usize = 1; pub struct Test {} @@ -84,6 +85,8 @@ impl Test { let dsl_marker = if flags.no_simplex { format!("not {SMPLX_NEXTEST_DSL_TEST_MARKER}") + } else if flags.proptest { + format!("not {SMPLX_NEXTEST_DSL_TEST_MARKER} and {SMPLX_NEXTEST_PROPTEST_DSL_TEST_MARKER}") } else { SMPLX_NEXTEST_DSL_TEST_MARKER.into() }; diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 15136045..6c2e3216 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -16,7 +16,17 @@ pub fn include_simf(tokenstream: TokenStream) -> TokenStream { pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::ItemFn); - match smplx_test::macros::expand(args.into(), input) { + match smplx_test::macros::expand_test(args.into(), input) { + Ok(ts) => ts.into(), + Err(e) => e.to_compile_error().into(), + } +} + +#[proc_macro_attribute] +pub fn proptest(args: TokenStream, input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::ItemFn); + + match smplx_test::macros::expand_proptest(args.into(), input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } diff --git a/crates/sdk/src/provider/esplora.rs b/crates/sdk/src/provider/esplora.rs index 8b403261..3e36cdbc 100644 --- a/crates/sdk/src/provider/esplora.rs +++ b/crates/sdk/src/provider/esplora.rs @@ -17,7 +17,7 @@ use super::core::{DEFAULT_ESPLORA_TIMEOUT_SECS, ProviderTrait}; use super::error::ProviderError; /// A provider implementation that interacts with the Esplora REST API backend. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EsploraProvider { /// The base URL of the Esplora REST API. pub esplora_url: String, diff --git a/crates/simplex/src/lib.rs b/crates/simplex/src/lib.rs index 009ec80e..6392f0b6 100644 --- a/crates/simplex/src/lib.rs +++ b/crates/simplex/src/lib.rs @@ -11,4 +11,4 @@ pub use smplx_test::context::TestContext; pub use smplx_test::mutantesting; pub use smplx_macros; -pub use smplx_macros::{include_simf, test}; +pub use smplx_macros::{include_simf, proptest, test}; diff --git a/crates/test/src/macros/core.rs b/crates/test/src/macros/core.rs index c6ecbbaf..acd0083d 100644 --- a/crates/test/src/macros/core.rs +++ b/crates/test/src/macros/core.rs @@ -8,19 +8,30 @@ macro_rules! smplx_test_marker { () => { "_smplx_test" }; + (prop) => { + "_smplx_proptest" + }; } pub const SMPLX_TEST_MARKER: &str = smplx_test_marker!(); +pub const SMPLX_PROPTEST_MARKER: &str = smplx_test_marker!(prop); type AttributeArgs = syn::punctuated::Punctuated; -pub fn expand(args: TokenStream, input: syn::ItemFn) -> syn::Result { +pub fn expand_test(args: TokenStream, input: syn::ItemFn) -> syn::Result { let parser = AttributeArgs::parse_terminated; let args = parser.parse2(args)?; expand_inner(&input, args) } +pub fn expand_proptest(args: TokenStream, input: syn::ItemFn) -> syn::Result { + let parser = AttributeArgs::parse_terminated; + let args = parser.parse2(args)?; + + expand_proptest_inner(&input, args) +} + // TODO: args? fn expand_inner(input: &syn::ItemFn, _args: AttributeArgs) -> syn::Result { let ret = &input.sig.output; @@ -57,3 +68,54 @@ fn expand_inner(input: &syn::ItemFn, _args: AttributeArgs) -> syn::Result syn::Result { + let ret = &input.sig.output; + let name = quote::format_ident!("{}_{}", &input.sig.ident.to_string(), SMPLX_PROPTEST_MARKER); + let inputs = &input.sig.inputs; + let body = &input.block; + let attrs = &input.attrs; + + let simplex_test_env = TEST_ENV_NAME; + + let expansion = quote::quote! { + #[::core::prelude::v1::test] + #(#attrs)* + fn #name() #ret { + use std::path::PathBuf; + use simplex::TestContext; + + fn #name(#inputs) #ret { + #body + } + + let config = mutantesting::proptest::test_runner::Config { + test_name: ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(#name) + )), + source_file: Some(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/", + stringify!(#name), + ".txt" + )), + ..Default::default() + }; + let test_context = match std::env::var(#simplex_test_env) { + Err(_) => { + panic!("Failed to run this test, required to use `simplex test`"); + }, + Ok(path) => { + TestContext::new(PathBuf::from(path)).unwrap() + } + }; + let fuzz_context_builder = FuzzStrategyBuilder::from_context(config, test_context); + #name(fuzz_context_builder) + } + }; + + Ok(expansion) +} diff --git a/crates/test/src/macros/mod.rs b/crates/test/src/macros/mod.rs index 9e9d50f0..b643ff46 100644 --- a/crates/test/src/macros/mod.rs +++ b/crates/test/src/macros/mod.rs @@ -1,3 +1,3 @@ pub mod core; -pub use core::expand; +pub use core::{expand_proptest, expand_test}; diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/mutantesting/core.rs index e2fc620f..ece5d053 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/mutantesting/core.rs @@ -7,7 +7,7 @@ use simplicityhl::{Arguments, WitnessValues}; use smplx_sdk::program::{Program, ProgramError, ProgramFactory}; use smplx_sdk::provider::{ProviderTrait, SimplicityNetwork}; -use smplx_sdk::signer::Signer; +use smplx_sdk::signer::{Signer, SignerError}; use smplx_sdk::transaction::FinalTransaction; use crate::context::TestContext; @@ -49,13 +49,36 @@ impl FuzzContext { self.test_context .as_ref() .as_ref() - .expect("TestContext has to be unempty") + .expect("TestContext has to be unempty in order to get a default signer") .get_default_signer(), ), SignerOption::CustomSigner => self.signer.as_ref().as_ref(), SignerOption::NoSigning => None, } } + + #[inline] + pub fn sign_or_extract(&self, ft: &FinalTransaction) -> Result { + match self.signer_option { + SignerOption::DefaultTestConfigSigner | SignerOption::CustomSigner => { + let signer = self.get_signer(); + Ok(signer.unwrap().sign_tx_raw(ft)?) + } + SignerOption::NoSigning => Ok(ft.extract_pst().0), + } + } +} + +pub trait ContractFuzzStrategyBlueprint { + type AdditionalInput: std::fmt::Debug + 'static; + + fn get_final_tx( + &self, + context: &FuzzContext, + args: &Arguments, + wit: &WitnessValues, + additional: &Self::AdditionalInput, + ) -> FinalTransaction; } pub trait ContractFuzzStrategy: std::fmt::Debug { diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs index 2d900045..58a8f560 100644 --- a/crates/test/src/mutantesting/engine.rs +++ b/crates/test/src/mutantesting/engine.rs @@ -1,86 +1,68 @@ -use std::cell::RefCell; +use std::fmt::Debug; use std::marker::PhantomData; use std::sync::Arc; -use proptest::prelude::{BoxedStrategy, TestCaseError}; +use proptest::prelude::{BoxedStrategy, Just, TestCaseError}; use proptest::strategy::Strategy; +use simplicityhl::elements::hashes::Hash; +use simplicityhl::{Arguments, WitnessValues}; + +use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; +use smplx_sdk::provider::{EsploraProvider, ProviderTrait, SimplicityNetwork}; +use smplx_sdk::signer::Signer; +use smplx_sdk::transaction::{FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature, UTXO}; + use crate::context::TestContext; +use crate::mutantesting::args_strategy::{Random, RandomValuePool}; use crate::mutantesting::core::{ - ContractFuzzStrategy, FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SignerOption, + ContractFuzzStrategy, ContractFuzzStrategyBlueprint, FuzzContext, FuzzableProgram, SignerOption, }; -use simplicityhl::elements::pset::PartiallySignedTransaction; -use simplicityhl::{Arguments, WitnessValues}; -use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; -use smplx_sdk::provider::{EsploraProvider, SimplicityNetwork}; -use smplx_sdk::signer::{Signer, SignerError}; -use smplx_sdk::transaction::FinalTransaction; +use crate::mutantesting::{ProgramCheck, ProgramExecResult}; -pub struct SimplexFuzzEngineInner { - pub(crate) fuzz_context: FuzzContext, - pub(crate) strategy_storage: Option>, - _phantom: PhantomData, +pub struct SimplexFuzzEngine { + runner: proptest::test_runner::TestRunner, + fuzz_context: FuzzContext, + strategy_storage: BoxedStrategy<((Arguments, WitnessValues), AdditionalValue)>, + blueprint: Box>, } -pub struct SimplexFuzzEngine { - runner: RefCell, - inner: RefCell>, +pub fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { + EsploraProvider::new("default_web_page.com".into(), default_network) } -#[allow(clippy::arc_with_non_send_sync)] -impl Default for FuzzContext { - fn default() -> Self { - let default_network = SimplicityNetwork::default_regtest(); - Self { - signer: Arc::new(None), - network: default_network, - mock_provider: Arc::new(get_default_provider(default_network)), - test_context: Arc::new(None), - signer_option: SignerOption::NoSigning, - } - } +pub struct FuzzStrategyBuilder { + proptest_config: proptest::test_runner::Config, + pub(crate) _strategy_storage: Option>, + pub(crate) _blueprint: Option>>, + pub(crate) signer_option: SignerOption, + pub(crate) signer: Option, + test_context: Option, + mock_provider: Arc, + network: SimplicityNetwork, + _phantom: PhantomData<(Program, Args, Wit)>, } -impl FuzzContext { - #[allow(clippy::arc_with_non_send_sync)] - pub fn with_signer(&mut self, signer: Signer) { - self.signer_option = SignerOption::CustomSigner; - self.signer = Arc::new(Some(signer)); - } - - #[allow(clippy::arc_with_non_send_sync)] - pub fn with_default_signer(&mut self) { - self.signer_option = SignerOption::DefaultTestConfigSigner; - } - - #[inline] - pub fn sign_or_extract(&self, ft: &FinalTransaction) -> Result { - match self.signer_option { - SignerOption::DefaultTestConfigSigner | SignerOption::CustomSigner => { - let signer = self.get_signer(); - Ok(signer.unwrap().sign_tx_raw(ft)?) - } - SignerOption::NoSigning => Ok(ft.extract_pst().0), - } - } -} -fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { - EsploraProvider::new("default_web_page.com".into(), default_network) -} - -impl SimplexFuzzEngine +impl FuzzStrategyBuilder where Program: FuzzableProgram + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + Add: std::fmt::Debug + Clone + 'static, { - pub fn from_config(mut config: proptest::test_runner::Config) -> Self { - config.cases = 500; + pub fn new(config: proptest::test_runner::Config) -> Self { + let default_network = SimplicityNetwork::default_regtest(); + Self { - runner: RefCell::new(proptest::test_runner::TestRunner::new(config)), - inner: RefCell::new(SimplexFuzzEngineInner { - fuzz_context: FuzzContext::default(), - strategy_storage: None, - _phantom: PhantomData, - }), + proptest_config: config, + _strategy_storage: None, + _blueprint: None, + test_context: None, + signer_option: SignerOption::NoSigning, + signer: None, + mock_provider: Arc::new(get_default_provider(default_network)), + network: default_network, + _phantom: PhantomData, } } @@ -108,70 +90,323 @@ where } Self { - runner: RefCell::new(proptest::test_runner::TestRunner::new(config)), - inner: RefCell::new(SimplexFuzzEngineInner { - fuzz_context: FuzzContext { - #[allow(clippy::arc_with_non_send_sync)] - signer: Arc::new(None), - #[allow(clippy::arc_with_non_send_sync)] - mock_provider: Arc::new(get_default_provider(default_network)), - #[allow(clippy::arc_with_non_send_sync)] - test_context: Arc::new(Some(test_context)), - signer_option: SignerOption::NoSigning, - network: SimplicityNetwork::Liquid, - }, - strategy_storage: None, - _phantom: PhantomData, - }), + proptest_config: config, + _strategy_storage: None, + _blueprint: None, + test_context: None, + signer_option: SignerOption::DefaultTestConfigSigner, + signer: None, + mock_provider: Arc::new(get_default_provider(default_network)), + network: default_network, + _phantom: PhantomData, } } +} + +impl FuzzStrategyBuilder +where + Program: FuzzableProgram + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + AdditionalValue: Debug + Clone, +{ + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer_option = SignerOption::CustomSigner; + self.signer = Some(signer); + self + } + + pub fn with_default_signer(mut self) -> Self { + self.signer_option = SignerOption::DefaultTestConfigSigner; + self + } - pub fn with_signer(&self, signer: Signer) { - self.inner.borrow_mut().fuzz_context.with_signer(signer); + pub fn with_no_signer(mut self) -> Self { + self.signer_option = SignerOption::NoSigning; + self } +} - pub fn with_default_signer(&self) { - self.inner.borrow_mut().fuzz_context.with_default_signer(); +impl FuzzStrategyBuilder +where + Program: FuzzableProgram + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + AdditionalValue: Debug + Clone, +{ + pub fn build( + self, + strategy_storage: impl Strategy + 'static, + blueprint: impl ContractFuzzStrategyBlueprint + 'static, + ) -> SimplexFuzzEngine { + SimplexFuzzEngine { + runner: proptest::test_runner::TestRunner::new(self.proptest_config), + fuzz_context: FuzzContext { + #[allow(clippy::arc_with_non_send_sync)] + signer: Arc::new(self.signer), + mock_provider: self.mock_provider, + #[allow(clippy::arc_with_non_send_sync)] + test_context: Arc::new(self.test_context), + signer_option: self.signer_option, + network: self.network, + }, + strategy_storage: strategy_storage.boxed(), + blueprint: Box::new(blueprint), + } } +} - pub fn with_final_arg_gen_strategy( +pub type FuzzTxBluepirintStep = + dyn Fn(&mut FinalTransaction, &FuzzContext, &Arguments, &WitnessValues, &AdditionalValue) + Send + Sync + 'static; + +pub struct FuzzTxBlueprint { + pub(crate) steps: Vec>>, + _phantom: PhantomData<(Program, Args, Wit)>, +} + +impl ContractFuzzStrategyBlueprint for S +where + S: ContractFuzzStrategy + 'static, + Program: FuzzableProgram + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + S::AdditionalInput: std::fmt::Debug + Clone + 'static, +{ + type AdditionalInput = S::AdditionalInput; + + fn get_final_tx( &self, - arg_gen: impl Strategy + 'static, - ft_gen: S, - ) where - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - S: ContractFuzzStrategy + 'static, - { - let context = self.inner.borrow().fuzz_context.clone(); - let mapped = arg_gen.prop_map(move |(args, wit)| ft_gen.gen_final_transaction(context.clone(), args, wit, ())); - self.inner.borrow_mut().strategy_storage = Some(mapped.boxed()); + context: &FuzzContext, + args: &Arguments, + wit: &WitnessValues, + additional: &Self::AdditionalInput, + ) -> FinalTransaction { + let (_, _, ft) = self.gen_final_transaction(context.clone(), args.clone(), wit.clone(), additional.clone()); + ft } +} + +pub struct FnBlueprintWrapper { + f: F, + _phantom: PhantomData<(Args, Wit, Add)>, +} - pub fn with_final_arg_gen_strategy_ext( +impl ContractFuzzStrategyBlueprint + for FnBlueprintWrapper +where + F: Fn(&FuzzContext, &Arguments, &WitnessValues, &AdditionalInput) -> FinalTransaction + 'static, + Program: FuzzableProgram + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, + AdditionalInput: std::fmt::Debug + Clone + 'static, +{ + type AdditionalInput = AdditionalInput; + + fn get_final_tx( &self, - arg_gen: impl Strategy + 'static, - ft_gen: S, - ) where - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - S: ContractFuzzStrategy + 'static, + context: &FuzzContext, + args: &Arguments, + wit: &WitnessValues, + additional: &Self::AdditionalInput, + ) -> FinalTransaction { + (self.f)(context, args, wit, additional) + } +} + +impl ContractFuzzStrategyBlueprint + for FuzzTxBlueprint +where + Program: FuzzableProgram + Clone + 'static, + Add: std::fmt::Debug + Clone + 'static, +{ + type AdditionalInput = Add; + + fn get_final_tx( + &self, + context: &FuzzContext, + args: &Arguments, + wit: &WitnessValues, + additional: &Self::AdditionalInput, + ) -> FinalTransaction { + let mut ft = FinalTransaction::new(); + for step in &self.steps { + step(&mut ft, context, args, wit, additional); + } + ft + } +} + +impl FuzzTxBlueprint +where + Program: FuzzableProgram + Clone + 'static, + Add: std::fmt::Debug + Clone + 'static, +{ + pub fn new() -> Self { + Self { + steps: Vec::new(), + _phantom: PhantomData, + } + } + + pub fn add_program_input(mut self, amount: u64) -> Self { + self.steps + .push(Arc::new(move |ft, context, arguments, witness, _additional| { + let (prog, script) = Program::build_program(arguments.clone(), &context.network); + let mut txout = simplicityhl::elements::TxOut::new_fee(amount, context.network.policy_asset()); + txout.script_pubkey = script; + + let partial_input = PartialInput::new(UTXO { + outpoint: simplicityhl::elements::OutPoint::new(simplicityhl::elements::Txid::all_zeros(), 0), + txout, + secrets: None, + }); + let program_input = ProgramInput::new(Box::new(prog.as_ref().as_ref().clone()), witness.clone()); + + ft.add_program_input(partial_input, program_input, RequiredSignature::None); + })); + self + } + + pub fn add_static_input(mut self, partial_input: PartialInput, required_sig: RequiredSignature) -> Self { + self.steps + .push(Arc::new(move |ft, _context, _arguments, _witness, _additional| { + ft.add_input(partial_input.clone(), required_sig.clone()); + })); + self + } + + pub fn add_static_output(mut self, partial_output: PartialOutput) -> Self { + self.steps + .push(Arc::new(move |ft, _context, _arguments, _witness, _additional| { + ft.add_output(partial_output.clone()); + })); + self + } + + pub fn add_custom_step(mut self, step: F) -> Self + where + F: Fn(&mut FinalTransaction, &FuzzContext, &Arguments, &WitnessValues, &Add) + Send + Sync + 'static, { - let context = self.inner.borrow().fuzz_context.clone(); - let mapped = arg_gen.prop_map(move |((args, wit), additional)| { - ft_gen.gen_final_transaction(context.clone(), args, wit, additional) - }); - self.inner.borrow_mut().strategy_storage = Some(mapped.boxed()); + self.steps.push(Arc::new(step)); + self } +} + +pub struct StrategyStorageBuilder { + base_strat: Option, + add_strat: Option, + + _placeholder: PhantomData<(Args, Wit)>, +} - pub fn run_with_check(self, program_check_fn: impl ProgramCheck) { - let mut runner = self.runner.into_inner(); - let inner = self.inner.into_inner(); +impl Default for StrategyStorageBuilder> { + fn default() -> Self { + Self::new() + } +} + +impl StrategyStorageBuilder> { + pub fn new() -> Self { + Self { + base_strat: None, + add_strat: Some(Just(())), + _placeholder: Default::default(), + } + } +} - let context = inner.fuzz_context; - let strategy = inner.strategy_storage.expect("Strategy must be configured"); +impl Default for FuzzTxBlueprint +where + Program: FuzzableProgram + Clone + 'static, + Add: std::fmt::Debug + Clone + 'static, +{ + fn default() -> Self { + Self::new() + } +} + +impl StrategyStorageBuilder { + pub fn with_random(self) -> StrategyStorageBuilder, AddStrat> { + StrategyStorageBuilder { + base_strat: Some(Random::::default()), + add_strat: self.add_strat, + _placeholder: Default::default(), + } + } + + pub fn with_random_pool(self) -> StrategyStorageBuilder, AddStrat> { + StrategyStorageBuilder { + base_strat: Some(RandomValuePool::::default()), + add_strat: self.add_strat, + _placeholder: Default::default(), + } + } + + pub fn with_custom_strategy( + self, + custom_strat: NewStrat, + ) -> StrategyStorageBuilder + where + NewStrat: Strategy + 'static, + { + StrategyStorageBuilder { + base_strat: Some(custom_strat), + add_strat: self.add_strat, + _placeholder: Default::default(), + } + } + + pub fn with_additional_strategy( + self, + strategy: NewAddStrat, + ) -> StrategyStorageBuilder + where + NewAddStrat: Strategy + 'static, + { + StrategyStorageBuilder { + base_strat: self.base_strat, + add_strat: Some(strategy), + _placeholder: Default::default(), + } + } + + pub fn with_random_asset_value(self) -> StrategyStorageBuilder> { + StrategyStorageBuilder { + base_strat: self.base_strat, + add_strat: Some(0..u64::MAX), + _placeholder: Default::default(), + } + } +} + +impl StrategyStorageBuilder +where + BaseStrat: Strategy + 'static, + AddStrat: Strategy + 'static, +{ + pub fn build(self) -> BoxedStrategy<((Arguments, WitnessValues), AddStrat::Value)> { + let base = self + .base_strat + .expect("Base strategy is mandatory. Call with_random() or similar."); + let add = self.add_strat.expect("Additional strategy is missing."); + + (base, add).boxed() + } +} + +impl SimplexFuzzEngine +where + Program: FuzzableProgram + Clone + 'static, + AdditionalValue: Clone + Debug + 'static, +{ + pub fn run_with_check(self, program_post_hook: impl ProgramCheck) { + let mut runner = self.runner; + let context = self.fuzz_context; + let strategy = self.strategy_storage; + let blueprint = self.blueprint; - match runner.run(&strategy, |(args, wit, ft)| { + match runner.run(&strategy, |((args, wit), add)| { + let ft = blueprint.get_final_tx(&context, &args, &wit, &add); let pst = context.sign_or_extract(&ft).unwrap(); let (failure_program, _script) = Program::build_program(args.clone(), &context.network); @@ -184,7 +419,7 @@ where .as_ref() .as_ref() .execute(&pst, &wit, i, &context.network); - if let Err(e) = program_check_fn.call(&context, &pst, &args, &wit, i, exec_result) { + if let Err(e) = program_post_hook.call(&context, &pst, &args, &wit, i, exec_result) { return Err(TestCaseError::fail(e)); } } diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/mutantesting/mod.rs index 8256ec91..40c13db0 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/mutantesting/mod.rs @@ -6,5 +6,5 @@ pub mod utils; pub use proptest; pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; -pub use engine::SimplexFuzzEngine; +pub use engine::{FuzzStrategyBuilder, SimplexFuzzEngine, get_default_provider}; pub use utils::generate_value_by_ty; diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index e4b6e679..77bf4475 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -1,15 +1,16 @@ mod failure_test_prop { + use crate::simple_storage_test_prop::SimpleStorageCheck; use simplex::mutantesting; - use simplex::mutantesting::args_strategy::{Random, RandomValuePool}; use simplex::mutantesting::core::ContractFuzzStrategy; - use simplex::mutantesting::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine}; + use simplex::mutantesting::engine::StrategyStorageBuilder; + use simplex::mutantesting::{FuzzContext, FuzzStrategyBuilder, FuzzableProgram, ProgramCheck, ProgramExecResult}; use simplex::simplicityhl::elements::hashes::Hash; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; - use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; use simplex_fixtures::artifacts::failure_test::FailureTestProgram; + use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; struct FailureTestCheck; @@ -94,14 +95,14 @@ mod failure_test_prop { ..Default::default() }; - let fuzz_engine = SimplexFuzzEngine::::from_config(config); - - fuzz_engine.with_final_arg_gen_strategy( - Random::::default(), - FailureGenStrategy::default(), - ); + let fuzz_engine = + FuzzStrategyBuilder::::new(config); - fuzz_engine.run_with_check(FailureTestCheck); + let strategy_storage = StrategyStorageBuilder::::new() + .with_random() + .build(); + let runner = fuzz_engine.with_no_signer().build(strategy_storage, FailureGenStrategy); + runner.run_with_check(SimpleStorageCheck); Ok(()) } @@ -124,39 +125,40 @@ mod failure_test_prop { ..Default::default() }; - let fuzz_engine = SimplexFuzzEngine::::from_config(config); + let fuzz_engine = + FuzzStrategyBuilder::::new(config); - fuzz_engine.with_final_arg_gen_strategy( - RandomValuePool::::default(), - FailureGenStrategy::default(), - ); - - fuzz_engine.run_with_check(FailureTestCheck); + let strategy_storage = StrategyStorageBuilder::::new() + .with_random_pool() + .build(); + let runner = fuzz_engine.with_no_signer().build(strategy_storage, FailureGenStrategy); + runner.run_with_check(SimpleStorageCheck); Ok(()) } } mod simple_storage_test_prop { - use simplex::mutantesting; - use simplex::mutantesting::args_strategy::Random; use simplex::mutantesting::core::ContractFuzzStrategy; - use simplex::mutantesting::{ - FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult, SimplexFuzzEngine, - }; + use simplex::mutantesting::engine::{StrategyStorageBuilder, get_default_provider}; + use simplex::mutantesting::{FuzzContext, FuzzStrategyBuilder, FuzzableProgram, ProgramCheck, ProgramExecResult}; use simplex::program::{ArgumentsTrait, WitnessTrait}; + use simplex::provider::SimplicityNetwork; + use simplex::signer::Signer; + use simplex::simplicityhl::elements::AssetId; use simplex::simplicityhl::elements::hashes::Hash; - use simplex::simplicityhl::elements::pset::serialize::Serialize; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; - use simplex::simplicityhl::elements::AssetId; + use simplex::simplicityhl::elements::pset::serialize::Serialize; use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; use simplex::transaction::PartialOutput; use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; + use simplex::{TestContext, mutantesting}; + use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; use simplex_fixtures::artifacts::simple_storage::derived_simple_storage::{ SimpleStorageArguments, SimpleStorageWitness, }; - use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; + use std::path::PathBuf; pub struct SimpleStorageCheck; @@ -184,7 +186,7 @@ mod simple_storage_test_prop { impl ContractFuzzStrategy for SimpleStorageStrategy { - type AdditionalInput = (u64, u64); + type AdditionalInput = u64; fn gen_final_transaction( &self, @@ -197,7 +199,14 @@ mod simple_storage_test_prop { let mut args_typed = SimpleStorageArguments::from_arguments(&arguments).unwrap(); let mut wit_typed = SimpleStorageWitness::from_witness(&witness).unwrap(); let signer = test_context.get_signer(); - let (new_value, old_value) = additional; + let mut old_value = additional; + let new_value = if old_value == u64::MAX { + let res = old_value; + old_value -= 1; + res + } else { + old_value + 1 + }; wit_typed.new_value = new_value; @@ -251,7 +260,7 @@ mod simple_storage_test_prop { }); // TODO: how to make correctly here? - let pst = test_context.sign_or_extract( &ft).unwrap(); + let pst = test_context.sign_or_extract(&ft).unwrap(); let wit_signed = signer .as_ref() .unwrap() @@ -287,22 +296,127 @@ mod simple_storage_test_prop { ..Default::default() }; - let fuzz_engine = SimplexFuzzEngine::::from_config(config); + let fuzz_context_builder = FuzzStrategyBuilder::new(config); + + let strategy_storage = StrategyStorageBuilder::::new() + .with_random() + .with_random_asset_value() + .build(); + let runner = fuzz_context_builder + .with_signer({ + const DEFAULT_REGTEST_MNEMONIC: &str = + "exist carry drive collect lend cereal occur much tiger just involve mean"; + Signer::new( + DEFAULT_REGTEST_MNEMONIC, + Box::new(get_default_provider(SimplicityNetwork::default_regtest())), + ) + }) + .build(strategy_storage, SimpleStorageStrategy); + runner.run_with_check(SimpleStorageCheck); + + Ok(()) + } + + #[ignore] + #[test] + fn possible_interface_simple_program__smplx_test() -> anyhow::Result<()> { + fn possible_interface_simple_program__smplx_test( + builder: FuzzStrategyBuilder, + ) -> anyhow::Result<()> { + let strategy_storage = StrategyStorageBuilder::::new() + .with_random() + .with_random_asset_value() + .build(); + let runner = builder + .with_signer({ + const DEFAULT_REGTEST_MNEMONIC: &str = + "exist carry drive collect lend cereal occur much tiger just involve mean"; + Signer::new( + DEFAULT_REGTEST_MNEMONIC, + Box::new(get_default_provider(SimplicityNetwork::default_regtest())), + ) + }) + .build(strategy_storage, SimpleStorageStrategy); + runner.run_with_check(SimpleStorageCheck); + + Ok(()) + } + + const SIMPLEX_PROP_TEST_ENV: &str = "SIMPLEX_RUN_PROP_TESTS"; + + if std::env::var(SIMPLEX_PROP_TEST_ENV).is_ok() { + let test_context = match std::env::var("SIMPLEX_TEST_ENV") { + Err(_) => { + panic!("Failed to run this test, required to use `simplex test`"); + } + Ok(path) => TestContext::new(PathBuf::from(path)).unwrap(), + }; + + let config = mutantesting::proptest::test_runner::Config { + test_name: ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_simple_program__smplx_test) + )), + source_file: Some(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/", + stringify!(possible_interface_simple_program__smplx_test), + ".txt" + )), + ..Default::default() + }; + let fuzz_context_builder = FuzzStrategyBuilder::from_context(config, test_context); - fuzz_engine.with_default_signer(); - fuzz_engine.with_final_arg_gen_strategy_ext( - ( - Random::::default(), - ( - 0_u64..((u32::MAX / 2) as u64), - ((u32::MAX / 2) as u64)..(u32::MAX as u64), - ), - ), - SimpleStorageStrategy::default(), - ); + possible_interface_simple_program__smplx_test(fuzz_context_builder) + } else { + eprintln!( + "Set '--proptest' flag in simplex to run a {} proptest", + stringify!(possible_interface_simple_program__smplx_test) + ); + + let config = mutantesting::proptest::test_runner::Config { + test_name: ::core::option::Option::Some(::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(possible_interface_simple_program__smplx_test) + )), + source_file: Some(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/", + stringify!(possible_interface_simple_program__smplx_test), + ".txt" + )), + ..Default::default() + }; + let fuzz_context_builder = FuzzStrategyBuilder::new(config); - fuzz_engine.run_with_check(SimpleStorageCheck); + possible_interface_simple_program__smplx_test(fuzz_context_builder) + // todo!() + } + } + + #[simplex::proptest] + fn possible_interface_simple_program_2( + fuzz_engine: FuzzStrategyBuilder, + ) -> anyhow::Result<()> { + let strategy_storage = StrategyStorageBuilder::::new() + .with_random_pool() + .with_random_asset_value() + .build(); + // let blueprint = FuzzTxBlueprint::new().add_program_input().add_custom_step(); + let runner = fuzz_engine + .with_signer({ + const DEFAULT_REGTEST_MNEMONIC: &str = + "exist carry drive collect lend cereal occur much tiger just involve mean"; + Signer::new( + DEFAULT_REGTEST_MNEMONIC, + Box::new(get_default_provider(SimplicityNetwork::default_regtest())), + ) + }) + .build(strategy_storage, SimpleStorageStrategy); + runner.run_with_check(SimpleStorageCheck); Ok(()) } } From 4659e62449e5f3d3663f8dee902047178dc26ffe Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 30 Jun 2026 14:25:25 +0300 Subject: [PATCH 18/21] feat: add new traits alignment to rely on proptest::TestRunner macros: * rename proptest -> fuzz * remove redundant #[must_use] * change naming of tests under the hood for fuzzing provider: remove unused Clone implementation strategies: * fragment and separate them into separate crates * add Interesting strategy fuzz_engine: * add FinalTransactionBuilder, which uses dummy program as input fixtures: * remove SimpleStorage example (very complex by now) --- crates/build/src/generator.rs | 1 - crates/build/src/macros/codegen.rs | 2 - crates/cli/src/commands/core.rs | 6 +- crates/cli/src/commands/test.rs | 6 +- crates/macros/src/lib.rs | 4 +- crates/sdk/src/provider/esplora.rs | 2 +- crates/simplex/src/lib.rs | 4 +- crates/test/src/fuzz/args_strategy.rs | 7 + crates/test/src/fuzz/args_strategy/random.rs | 55 +++ .../fuzz/args_strategy/random_interesting.rs | 332 ++++++++++++++ .../args_strategy/random_pool.rs} | 51 +-- crates/test/src/fuzz/builders.rs | 5 + .../test/src/fuzz/builders/dummy_program.rs | 225 +++++++++ .../src/fuzz/builders/final_tx_builder.rs | 138 ++++++ .../test/src/{mutantesting => fuzz}/core.rs | 28 +- crates/test/src/fuzz/engine.rs | 328 +++++++++++++ crates/test/src/{mutantesting => fuzz}/mod.rs | 3 +- .../test/src/{mutantesting => fuzz}/utils.rs | 4 +- crates/test/src/lib.rs | 2 +- crates/test/src/macros/core.rs | 18 +- crates/test/src/macros/mod.rs | 2 +- crates/test/src/mutantesting/engine.rs | 433 ------------------ fixtures/tests/prop_testing.rs | 368 ++------------- 23 files changed, 1160 insertions(+), 864 deletions(-) create mode 100644 crates/test/src/fuzz/args_strategy.rs create mode 100644 crates/test/src/fuzz/args_strategy/random.rs create mode 100644 crates/test/src/fuzz/args_strategy/random_interesting.rs rename crates/test/src/{mutantesting/args_strategy.rs => fuzz/args_strategy/random_pool.rs} (80%) create mode 100644 crates/test/src/fuzz/builders.rs create mode 100644 crates/test/src/fuzz/builders/dummy_program.rs create mode 100644 crates/test/src/fuzz/builders/final_tx_builder.rs rename crates/test/src/{mutantesting => fuzz}/core.rs (78%) create mode 100644 crates/test/src/fuzz/engine.rs rename crates/test/src/{mutantesting => fuzz}/mod.rs (71%) rename crates/test/src/{mutantesting => fuzz}/utils.rs (100%) delete mode 100644 crates/test/src/mutantesting/engine.rs diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index b63aefa7..5967d4b3 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -279,7 +279,6 @@ impl ArtifactsGenerator { self } - #[must_use] pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { self.program.set_storage_at(index, new_value); } diff --git a/crates/build/src/macros/codegen.rs b/crates/build/src/macros/codegen.rs index d9090599..923b6a91 100644 --- a/crates/build/src/macros/codegen.rs +++ b/crates/build/src/macros/codegen.rs @@ -178,7 +178,6 @@ impl WitnessStruct { impl simplex::program::ArgumentsTrait for #struct_name { /// Build Simplicity arguments for contract instantiation. - #[must_use] fn build_arguments(&self) -> simplex::simplicityhl::Arguments { simplex::simplicityhl::Arguments::from(HashMap::from([ #(#tuples),* @@ -280,7 +279,6 @@ impl WitnessStruct { impl simplex::program::WitnessTrait for #struct_name { /// Build Simplicity witness values for contract execution. - #[must_use] fn build_witness(&self) -> simplex::simplicityhl::WitnessValues { simplex::simplicityhl::WitnessValues::from(HashMap::from([ #(#tuples),* diff --git a/crates/cli/src/commands/core.rs b/crates/cli/src/commands/core.rs index 0eeb93ae..5735240f 100644 --- a/crates/cli/src/commands/core.rs +++ b/crates/cli/src/commands/core.rs @@ -62,7 +62,7 @@ pub struct TestFlags { /// Run non-simplex tests (may be used for running unit tests) #[arg(long = "no-simplex")] pub no_simplex: bool, - /// Run proptests via simplex - #[arg(long = "proptest")] - pub proptest: bool, + /// Perform fuzzing via simplex + #[arg(long = "fuzz")] + pub fuzz: bool, } diff --git a/crates/cli/src/commands/test.rs b/crates/cli/src/commands/test.rs index 0df60e20..ea9225b3 100644 --- a/crates/cli/src/commands/test.rs +++ b/crates/cli/src/commands/test.rs @@ -9,7 +9,7 @@ use super::error::CommandError; /// Nextest dsl variable to filter and use only simplex tests const SMPLX_NEXTEST_DSL_TEST_MARKER: &str = concat!("test(/", smplx_test_marker!(), "$/)"); -const SMPLX_NEXTEST_PROPTEST_DSL_TEST_MARKER: &str = concat!("test(/", smplx_test_marker!(prop), "$/)"); +const SMPLX_NEXTEST_FUZZ_DSL_TEST_MARKER: &str = concat!("test(/", smplx_test_marker!(fuzz), "$/)"); const DEFAULT_THREADS_NUMBER: usize = 1; pub struct Test {} @@ -85,8 +85,8 @@ impl Test { let dsl_marker = if flags.no_simplex { format!("not {SMPLX_NEXTEST_DSL_TEST_MARKER}") - } else if flags.proptest { - format!("not {SMPLX_NEXTEST_DSL_TEST_MARKER} and {SMPLX_NEXTEST_PROPTEST_DSL_TEST_MARKER}") + } else if flags.fuzz { + format!("not {SMPLX_NEXTEST_DSL_TEST_MARKER} and {SMPLX_NEXTEST_FUZZ_DSL_TEST_MARKER}") } else { SMPLX_NEXTEST_DSL_TEST_MARKER.into() }; diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 6c2e3216..6842b40f 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -23,10 +23,10 @@ pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -pub fn proptest(args: TokenStream, input: TokenStream) -> TokenStream { +pub fn fuzz(args: TokenStream, input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::ItemFn); - match smplx_test::macros::expand_proptest(args.into(), input) { + match smplx_test::macros::expand_fuzz(args.into(), input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } diff --git a/crates/sdk/src/provider/esplora.rs b/crates/sdk/src/provider/esplora.rs index 3e36cdbc..8b403261 100644 --- a/crates/sdk/src/provider/esplora.rs +++ b/crates/sdk/src/provider/esplora.rs @@ -17,7 +17,7 @@ use super::core::{DEFAULT_ESPLORA_TIMEOUT_SECS, ProviderTrait}; use super::error::ProviderError; /// A provider implementation that interacts with the Esplora REST API backend. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct EsploraProvider { /// The base URL of the Esplora REST API. pub esplora_url: String, diff --git a/crates/simplex/src/lib.rs b/crates/simplex/src/lib.rs index 6392f0b6..5b7f598a 100644 --- a/crates/simplex/src/lib.rs +++ b/crates/simplex/src/lib.rs @@ -8,7 +8,7 @@ pub use smplx_sdk::*; pub use smplx_test::config::TestConfig; pub use smplx_test::context::TestContext; -pub use smplx_test::mutantesting; +pub use smplx_test::fuzz; pub use smplx_macros; -pub use smplx_macros::{include_simf, proptest, test}; +pub use smplx_macros::{fuzz, include_simf, test}; diff --git a/crates/test/src/fuzz/args_strategy.rs b/crates/test/src/fuzz/args_strategy.rs new file mode 100644 index 00000000..ae56c221 --- /dev/null +++ b/crates/test/src/fuzz/args_strategy.rs @@ -0,0 +1,7 @@ +mod random; +mod random_interesting; +mod random_pool; + +pub use random::*; +pub use random_interesting::*; +pub use random_pool::*; diff --git a/crates/test/src/fuzz/args_strategy/random.rs b/crates/test/src/fuzz/args_strategy/random.rs new file mode 100644 index 00000000..0b59cec0 --- /dev/null +++ b/crates/test/src/fuzz/args_strategy/random.rs @@ -0,0 +1,55 @@ +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; + +use simplicityhl::{Arguments, WitnessValues}; + +use proptest::prelude::Strategy; +use proptest::strategy::{NewTree, ValueTree}; +use proptest::test_runner::TestRunner; + +use smplx_sdk::program::{RandomArguments, RandomWitness}; + +pub struct Random { + phantom_data: PhantomData<(Args, Wit)>, +} + +impl Default for Random { + fn default() -> Self { + Self { + phantom_data: PhantomData, + } + } +} + +impl Debug for Random { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Random trees...") + } +} + +pub struct RandomValueTree(T); + +impl ValueTree for RandomValueTree { + type Value = T; + fn current(&self) -> T { + self.0.clone() + } + fn simplify(&mut self) -> bool { + false + } + fn complicate(&mut self) -> bool { + false + } +} + +impl Strategy for Random { + type Tree = RandomValueTree<(Arguments, WitnessValues)>; + type Value = (Arguments, WitnessValues); + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + Ok(RandomValueTree(( + Args::generate_arguments(runner.rng()), + Wit::generate_witness(runner.rng()), + ))) + } +} diff --git a/crates/test/src/fuzz/args_strategy/random_interesting.rs b/crates/test/src/fuzz/args_strategy/random_interesting.rs new file mode 100644 index 00000000..ae1956a3 --- /dev/null +++ b/crates/test/src/fuzz/args_strategy/random_interesting.rs @@ -0,0 +1,332 @@ +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; + +use simplicityhl::num::U256; +use simplicityhl::types::{TypeInner, UIntType}; +use simplicityhl::value::ValueConstructible; +use simplicityhl::{Arguments, ResolvedType, Value, WitnessValues}; + +use proptest::prelude::Rng; +use proptest::prelude::Strategy; +use proptest::strategy::{NewTree, ValueTree}; +use proptest::test_runner::{TestRng, TestRunner}; +use rand::prelude::IndexedRandom; + +use smplx_sdk::program::{RandomArguments, RandomWitness}; + +static INTERESTING_U4: &[u8] = &[0, 1, 2, 7, 8, 14, 15]; + +static INTERESTING_U8: &[u8] = &[0, 1, 2, 16, 32, 64, 100, (1 << 7) - 1, 1 << 7, u8::MAX - 1, u8::MAX]; + +static INTERESTING_U16: &[u16] = &[ + 0, + 1, + 2, + 16, + 32, + 64, + 100, + 1000, + 1024, + 4096, + (1 << 7) - 1, + 1 << 7, + u8::MAX as u16, + 1 << 8, + (1 << 15) - 1, + 1 << 15, + u16::MAX - 1, + u16::MAX, +]; + +static INTERESTING_U32: &[u32] = &[ + 0, + 1, + 2, + 16, + 32, + 64, + 100, + 1000, + 1024, + 4096, + (1 << 7) - 1, + 1 << 7, + u8::MAX as u32, + 1 << 8, + (1 << 15) - 1, + 1 << 15, + u16::MAX as u32, + 1 << 16, + (1 << 31) - 1, + 1 << 31, + u32::MAX - 1, + u32::MAX, +]; + +static INTERESTING_U64: &[u64] = &[ + 0, + 1, + 2, + 16, + 32, + 64, + 100, + 1000, + 1024, + 4096, + (1 << 7) - 1, + 1 << 7, + u8::MAX as u64, + 1 << 8, + (1 << 15) - 1, + 1 << 15, + u16::MAX as u64, + 1 << 16, + (1 << 31) - 1, + 1 << 31, + u32::MAX as u64, + 1 << 32, + (1 << 63) - 1, + 1 << 63, + u64::MAX - 1, + u64::MAX, +]; + +static INTERESTING_U128: &[u128] = &[ + 0, + 1, + 2, + 16, + 32, + 64, + 100, + 1000, + 1024, + 4096, + (1 << 7) - 1, + 1 << 7, + u8::MAX as u128, + 1 << 8, + (1 << 15) - 1, + 1 << 15, + u16::MAX as u128, + 1 << 16, + (1 << 31) - 1, + 1 << 31, + u32::MAX as u128, + 1 << 32, + (1 << 63) - 1, + 1 << 63, + u64::MAX as u128, + 1 << 64, + (1_u128 << 127) - 1, + 1_u128 << 127, + u128::MAX - 1, + u128::MAX, +]; + +pub struct InterestingRandom { + phantom_data: PhantomData<(Args, Wit)>, +} + +impl Default for InterestingRandom { + fn default() -> Self { + Self { + phantom_data: PhantomData, + } + } +} + +impl Debug for InterestingRandom { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "InterestingRandom trees...") + } +} + +pub struct InterestingRandomValueTree(T); + +impl ValueTree for InterestingRandomValueTree { + type Value = T; + fn current(&self) -> T { + self.0.clone() + } + fn simplify(&mut self) -> bool { + false + } + fn complicate(&mut self) -> bool { + false + } +} + +impl Strategy + for InterestingRandom +{ + type Tree = InterestingRandomValueTree<(Arguments, WitnessValues)>; + type Value = (Arguments, WitnessValues); + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + let args = Args::generate_arguments(runner.rng()); + let wit = Wit::generate_witness(runner.rng()); + + let mut args_map = HashMap::new(); + for (name, val) in args.iter() { + args_map.insert( + name.clone(), + generate_interesting_or_scratch_by_ty(val.ty(), runner.rng()), + ); + } + + let mut wit_map = HashMap::new(); + for (name, val) in wit.iter() { + wit_map.insert( + name.clone(), + generate_interesting_or_scratch_by_ty(val.ty(), runner.rng()), + ); + } + + Ok(InterestingRandomValueTree(( + Arguments::from(args_map), + WitnessValues::from(wit_map), + ))) + } +} + +pub fn generate_interesting_or_scratch_by_ty(ty: &ResolvedType, rng: &mut TestRng) -> Value { + match ty.as_inner() { + TypeInner::Either(left_ty, right_ty) => { + let left_v: bool = rng.random(); + match left_v { + true => Value::left( + generate_interesting_or_scratch_by_ty(left_ty, rng), + (**right_ty).clone(), + ), + false => Value::right( + (**left_ty).clone(), + generate_interesting_or_scratch_by_ty(right_ty, rng), + ), + } + } + TypeInner::Option(option_ty) => { + let is_some: bool = rng.random(); + match is_some { + true => Value::some(generate_interesting_or_scratch_by_ty(option_ty, rng)), + false => Value::none((**option_ty).clone()), + } + } + TypeInner::Boolean => Value::from(rng.random::()), + TypeInner::UInt(x) => { + let use_interesting: bool = rng.random(); + if use_interesting { + match x { + UIntType::U1 => Value::u1(rng.random::() & 0x01), + UIntType::U2 => Value::u2(rng.random::() & 0x03), + UIntType::U4 => { + let val = *INTERESTING_U4.choose(rng).unwrap(); + Value::u4(val) + } + UIntType::U8 => { + let val = *INTERESTING_U8.choose(rng).unwrap(); + Value::u8(val) + } + UIntType::U16 => { + let val = *INTERESTING_U16.choose(rng).unwrap(); + Value::u16(val) + } + UIntType::U32 => { + let val = *INTERESTING_U32.choose(rng).unwrap(); + Value::u32(val) + } + UIntType::U64 => { + let val = *INTERESTING_U64.choose(rng).unwrap(); + Value::u64(val) + } + UIntType::U128 => { + let val = *INTERESTING_U128.choose(rng).unwrap(); + Value::u128(val) + } + UIntType::U256 => { + let val = *INTERESTING_U128.choose(rng).unwrap(); + let mut bytes = [0u8; 32]; + let val_bytes = val.to_be_bytes(); + bytes[16..32].copy_from_slice(&val_bytes); + Value::u256(U256::from_byte_array(bytes)) + } + } + } else { + match x { + UIntType::U1 => Value::u1(rng.random::() & 0x0001), + UIntType::U2 => Value::u2(rng.random::() & 0x0003), + UIntType::U4 => Value::u4(rng.random::() & 0x000F), + UIntType::U8 => Value::u8(rng.random::()), + UIntType::U16 => Value::u16(rng.random::()), + UIntType::U32 => Value::u32(rng.random::()), + UIntType::U64 => Value::u64(rng.random::()), + UIntType::U128 => Value::u128(rng.random::()), + UIntType::U256 => Value::u256(U256::from_byte_array(rng.random())), + } + } + } + TypeInner::Tuple(tuple_ty) => { + Value::tuple(tuple_ty.iter().map(|x| generate_interesting_or_scratch_by_ty(x, rng))) + } + TypeInner::Array(array_ty, size) => Value::array( + (0..*size).map(|_| generate_interesting_or_scratch_by_ty(array_ty, rng)), + (**array_ty).clone(), + ), + TypeInner::List(list_ty, size_pow_2) => { + let size = rng.random_range(0..size_pow_2.get()); + Value::list( + (0..size).map(|_| generate_interesting_or_scratch_by_ty(list_ty, rng)), + (**list_ty).clone(), + *size_pow_2, + ) + } + _ => Value::unit(), + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct InterestingU64Strategy; + +impl Strategy for InterestingU64Strategy { + type Tree = InterestingU64ValueTree; + type Value = u64; + + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { + let use_interesting: bool = runner.rng().random(); + let val = if use_interesting { + *INTERESTING_U64.choose(runner.rng()).unwrap() + } else { + runner.rng().random::() + }; + Ok(InterestingU64ValueTree { val }) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct InterestingU64ValueTree { + val: u64, +} + +impl ValueTree for InterestingU64ValueTree { + type Value = u64; + + fn current(&self) -> Self::Value { + self.val + } + + fn simplify(&mut self) -> bool { + if self.val > 0 { + self.val /= 2; + true + } else { + false + } + } + + fn complicate(&mut self) -> bool { + false + } +} diff --git a/crates/test/src/mutantesting/args_strategy.rs b/crates/test/src/fuzz/args_strategy/random_pool.rs similarity index 80% rename from crates/test/src/mutantesting/args_strategy.rs rename to crates/test/src/fuzz/args_strategy/random_pool.rs index af80be23..42281f9a 100644 --- a/crates/test/src/mutantesting/args_strategy.rs +++ b/crates/test/src/fuzz/args_strategy/random_pool.rs @@ -2,61 +2,16 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; +use simplicityhl::str::WitnessName; +use simplicityhl::{Arguments, ResolvedType, Value, WitnessValues}; + use proptest::prelude::Rng; use proptest::prelude::Strategy; use proptest::strategy::{NewTree, ValueTree}; use proptest::test_runner::{TestRng, TestRunner}; -use simplicityhl::str::WitnessName; -use simplicityhl::{Arguments, ResolvedType, Value, WitnessValues}; - use smplx_sdk::program::{RandomArguments, RandomWitness}; -pub struct Random { - phantom_data: PhantomData<(Args, Wit)>, -} - -impl Default for Random { - fn default() -> Self { - Self { - phantom_data: PhantomData, - } - } -} - -impl Debug for Random { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Random trees...") - } -} - -pub struct RandomValueTree(T); - -impl ValueTree for RandomValueTree { - type Value = T; - fn current(&self) -> T { - self.0.clone() - } - fn simplify(&mut self) -> bool { - false - } - fn complicate(&mut self) -> bool { - false - } -} - -impl Strategy for Random { - type Tree = RandomValueTree<(Arguments, WitnessValues)>; - type Value = (Arguments, WitnessValues); - - fn new_tree(&self, runner: &mut TestRunner) -> NewTree { - Ok(RandomValueTree(( - Args::generate_arguments(runner.rng()), - Wit::generate_witness(runner.rng()), - ))) - } -} - pub struct RandomValuePool { phantom_data: PhantomData<(Args, Wit)>, _value_pool: ValuePool, diff --git a/crates/test/src/fuzz/builders.rs b/crates/test/src/fuzz/builders.rs new file mode 100644 index 00000000..ced926dc --- /dev/null +++ b/crates/test/src/fuzz/builders.rs @@ -0,0 +1,5 @@ +mod dummy_program; +mod final_tx_builder; + +pub use dummy_program::*; +pub use final_tx_builder::*; diff --git a/crates/test/src/fuzz/builders/dummy_program.rs b/crates/test/src/fuzz/builders/dummy_program.rs new file mode 100644 index 00000000..3ae8ab94 --- /dev/null +++ b/crates/test/src/fuzz/builders/dummy_program.rs @@ -0,0 +1,225 @@ +use simplicityhl::Arguments; +use simplicityhl::elements::Script; +use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; + +use smplx_sdk::program::Program; +use smplx_sdk::provider::SimplicityNetwork; + +pub struct DummyProgram { + program: Program, +} +impl DummyProgram { + pub const SOURCE: &'static str = derived_dummy_program::DUMMY_PROGRAM_CONTRACT_SOURCE; + + #[must_use] + pub fn new(arguments: impl Into) -> Self { + Self { + program: Program::new(Self::SOURCE, arguments), + } + } + + #[must_use] + pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { + self.program = self.program.with_taproot_pubkey(pub_key); + self + } + + #[must_use] + pub fn with_storage_capacity(mut self, capacity: usize) -> Self { + self.program = self.program.with_storage_capacity(capacity); + self + } + + pub fn set_storage_at(&mut self, index: usize, new_value: [u8; 32]) { + self.program.set_storage_at(index, new_value); + } + + #[must_use] + pub fn get_storage_len(&self) -> usize { + self.program.get_storage_len() + } + + #[must_use] + pub fn get_storage(&self) -> &[[u8; 32]] { + self.program.get_storage() + } + + #[must_use] + pub fn get_storage_at(&self, index: usize) -> [u8; 32] { + self.program.get_storage_at(index) + } + + #[must_use] + pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script { + self.program.get_script_pubkey(network) + } + + #[must_use] + pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] { + self.program.get_script_hash(network) + } +} + +impl AsRef for DummyProgram { + fn as_ref(&self) -> &Program { + &self.program + } +} + +impl AsMut for DummyProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.program + } +} + +pub mod derived_dummy_program { + pub const DUMMY_PROGRAM_CONTRACT_SOURCE: &str = "mod unit_0 {\n fn main() {\n assert!(true);\n }\n}\n"; + + pub use build_witness::*; + + mod build_witness { + use rand::RngCore; + use simplicityhl::WitnessValues; + use smplx_sdk::program::WitnessTrait; + use std::collections::HashMap; + + #[derive(Debug, Clone, PartialEq, Eq, Default)] + pub struct DummyProgramWitness {} + + impl DummyProgramWitness { + ///Build struct from Simplicity `WitnessValues`. + /// + ///# Errors + /// + ///Returns error if any required witness is missing, has the wrong type, or has an invalid value. + pub fn from_witness(_witness: &WitnessValues) -> Result { + Ok(Self {}) + } + + ///Generate a random Witness struct instance using the provided RNG. + pub fn generate_witness_raw(_rng: &mut R) -> Self { + DummyProgramWitness {} + } + } + + impl smplx_sdk::program::WitnessTrait for DummyProgramWitness { + /// Build Simplicity witness values for contract execution. + fn build_witness(&self) -> simplicityhl::WitnessValues { + simplicityhl::WitnessValues::from(HashMap::from([])) + } + } + + impl serde::Serialize for DummyProgramWitness { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.build_witness().serialize(serializer) + } + } + + impl<'de> serde::Deserialize<'de> for DummyProgramWitness { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let x = simplicityhl::WitnessValues::deserialize(deserializer)?; + Self::from_witness(&x).map_err(serde::de::Error::custom) + } + } + + impl smplx_sdk::program::RandomWitness for DummyProgramWitness { + fn generate_witness(rng: &mut dyn RngCore) -> simplicityhl::WitnessValues { + Self::generate_witness_raw(rng).build_witness() + } + } + + impl From for simplicityhl::WitnessValues { + fn from(val: DummyProgramWitness) -> simplicityhl::WitnessValues { + val.build_witness() + } + } + } + + pub use build_arguments::*; + + mod build_arguments { + use std::collections::HashMap; + + use simplicityhl::Arguments; + + use rand::RngCore; + + use smplx_sdk::program::ArgumentsTrait; + #[derive(Debug, Clone, PartialEq, Eq, Default)] + pub struct DummyProgramArguments {} + + impl DummyProgramArguments { + ///Build struct from Simplicity `Arguments`. + /// + ///# Errors + /// + ///Returns error if any required witness is missing, has the wrong type, or has an invalid value. + pub fn from_arguments(_args: &Arguments) -> Result { + Ok(Self {}) + } + + /// Generate a random Arguments struct instance using the provided RNG. + pub fn generate_arguments_raw(_rng: &mut R) -> Self { + DummyProgramArguments {} + } + } + + impl smplx_sdk::program::ArgumentsTrait for DummyProgramArguments { + /// Build Simplicity arguments for contract instantiation. + fn build_arguments(&self) -> simplicityhl::Arguments { + simplicityhl::Arguments::from(HashMap::from([])) + } + } + + impl serde::Serialize for DummyProgramArguments { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.build_arguments().serialize(serializer) + } + } + + impl<'de> serde::Deserialize<'de> for DummyProgramArguments { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let x = simplicityhl::Arguments::deserialize(deserializer)?; + Self::from_arguments(&x).map_err(serde::de::Error::custom) + } + } + + impl smplx_sdk::program::RandomArguments for DummyProgramArguments { + fn generate_arguments(rng: &mut dyn RngCore) -> simplicityhl::Arguments { + Self::generate_arguments_raw(rng).build_arguments() + } + } + + impl From for simplicityhl::Arguments { + fn from(val: DummyProgramArguments) -> simplicityhl::Arguments { + val.build_arguments() + } + } + } + + mod program_helpers { + use simplicityhl::Arguments; + + use smplx_sdk::program::ProgramFactory; + + use super::super::DummyProgram; + + impl ProgramFactory for DummyProgram { + fn instantiate_program(args: impl Into) -> Box { + Box::new(DummyProgram::new(args)) + } + } + } +} diff --git a/crates/test/src/fuzz/builders/final_tx_builder.rs b/crates/test/src/fuzz/builders/final_tx_builder.rs new file mode 100644 index 00000000..a6fbecf0 --- /dev/null +++ b/crates/test/src/fuzz/builders/final_tx_builder.rs @@ -0,0 +1,138 @@ +use crate::fuzz::FuzzableProgram; +use crate::fuzz::builders::dummy_program; +use crate::fuzz::builders::dummy_program::derived_dummy_program::{DummyProgramArguments, DummyProgramWitness}; + +use simplicityhl::elements::hashes::Hash; +use simplicityhl::{Arguments, WitnessValues}; + +use proptest::prelude::{BoxedStrategy, Strategy}; +use proptest::strategy::ValueTree; +use proptest::test_runner::TestRunner; + +use smplx_sdk::program::{ProgramFactory, WitnessTrait}; +use smplx_sdk::provider::SimplicityNetwork; +use smplx_sdk::transaction::{FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature, UTXO}; + +#[derive(Clone, Debug)] +pub struct FinalTxMeta { + program_input_idxs: Vec, + program_output_idxs: Vec, + network: SimplicityNetwork, +} + +#[derive(Clone, Debug)] +pub struct FinalTransactionBuilder { + meta: FinalTxMeta, + ft: FinalTransaction, +} + +impl Default for FinalTransactionBuilder { + fn default() -> Self { + Self::new() + } +} + +impl FinalTransactionBuilder { + pub fn new() -> Self { + Self { + meta: FinalTxMeta { + program_input_idxs: vec![], + program_output_idxs: vec![], + network: SimplicityNetwork::default_regtest(), + }, + ft: FinalTransaction::new(), + } + } + + pub fn add_program_input(self, amount: Option) -> Self { + self.add_program_custom_input(amount, None, Some(RequiredSignature::None)) + } + + pub fn add_program_custom_input( + mut self, + amount: Option, + outpoint: Option, + required_signature: Option, + ) -> Self { + let last_input_idx = self.ft.inputs().len(); + if amount.is_none() { + self.meta.program_input_idxs.push(last_input_idx); + } + + let (prog, script) = + dummy_program::DummyProgram::build_program(DummyProgramArguments {}, &SimplicityNetwork::default_regtest()); + let mut txout = + simplicityhl::elements::TxOut::new_fee(amount.unwrap_or_default(), self.meta.network.policy_asset()); + txout.script_pubkey = script; + + let partial_input = PartialInput::new(UTXO { + outpoint: outpoint.unwrap_or(simplicityhl::elements::OutPoint::new( + simplicityhl::elements::Txid::all_zeros(), + 0, + )), + txout, + secrets: None, + }); + let program_input = ProgramInput::new( + Box::new(prog.as_ref().as_ref().clone()), + DummyProgramWitness {}.build_witness(), + ); + + self.ft.add_program_input( + partial_input, + program_input, + required_signature.unwrap_or(RequiredSignature::None), + ); + self + } + + pub fn randomize_input_value(mut self, idx: usize) -> Self { + self.meta.program_input_idxs.push(idx); + self + } + + pub fn randomize_output_value(mut self, idx: usize) -> Self { + self.meta.program_output_idxs.push(idx); + self + } + + pub fn add_static_input(mut self, partial_input: PartialInput, required_sig: RequiredSignature) -> Self { + self.ft.add_input(partial_input, required_sig); + self + } + + pub fn add_static_output(mut self, partial_output: PartialOutput) -> Self { + self.ft.add_output(partial_output); + self + } + + pub fn get_inputs_to_check(&self) -> &[usize] { + &self.meta.program_input_idxs + } + + pub fn get_strategies_to_make_initial_ft_valid(&self) -> Vec> { + vec![] + } + + pub fn insert_real_program_values + ProgramFactory + Clone + 'static>( + &self, + context: &mut TestRunner, + args_wit_strategy: &BoxedStrategy<(Arguments, WitnessValues)>, + ) -> (Arguments, WitnessValues, FinalTransaction) { + let mut ft = self.ft.clone(); + let (args, wit) = args_wit_strategy.new_tree(context).unwrap().current(); + let (prog_instance, script) = Program::build_program(args.clone(), &self.meta.network); + + for idx in self.meta.program_input_idxs.iter() { + let sdk_program = prog_instance.as_ref().as_ref().clone(); + let edit_input = &mut ft.inputs_mut()[*idx]; + edit_input.program_input = Some(ProgramInput::new(Box::new(sdk_program), wit.clone())); + edit_input.partial_input.witness_utxo.script_pubkey = script.clone(); + } + for idx in self.meta.program_output_idxs.iter() { + ft.outputs_mut()[*idx].script_pubkey = script.clone(); + } + + (args, wit, ft) + } +} diff --git a/crates/test/src/mutantesting/core.rs b/crates/test/src/fuzz/core.rs similarity index 78% rename from crates/test/src/mutantesting/core.rs rename to crates/test/src/fuzz/core.rs index ece5d053..321712ca 100644 --- a/crates/test/src/mutantesting/core.rs +++ b/crates/test/src/fuzz/core.rs @@ -11,6 +11,7 @@ use smplx_sdk::signer::{Signer, SignerError}; use smplx_sdk::transaction::FinalTransaction; use crate::context::TestContext; +use crate::fuzz::builders::FinalTransactionBuilder; #[derive(Clone, Debug)] pub(crate) enum SignerOption { @@ -23,6 +24,7 @@ pub(crate) enum SignerOption { pub struct FuzzContext { pub(crate) signer: Arc>, pub mock_provider: Arc, + /// Used for inserting signer pub(crate) test_context: Arc>, pub(crate) signer_option: SignerOption, pub network: SimplicityNetwork, @@ -59,7 +61,7 @@ impl FuzzContext { #[inline] pub fn sign_or_extract(&self, ft: &FinalTransaction) -> Result { - match self.signer_option { + match &self.signer_option { SignerOption::DefaultTestConfigSigner | SignerOption::CustomSigner => { let signer = self.get_signer(); Ok(signer.unwrap().sign_tx_raw(ft)?) @@ -69,28 +71,8 @@ impl FuzzContext { } } -pub trait ContractFuzzStrategyBlueprint { - type AdditionalInput: std::fmt::Debug + 'static; - - fn get_final_tx( - &self, - context: &FuzzContext, - args: &Arguments, - wit: &WitnessValues, - additional: &Self::AdditionalInput, - ) -> FinalTransaction; -} - -pub trait ContractFuzzStrategy: std::fmt::Debug { - type AdditionalInput: std::fmt::Debug + 'static; - - fn gen_final_transaction( - &self, - test_context: FuzzContext, - arguments: Arguments, - witness: WitnessValues, - additional: Self::AdditionalInput, - ) -> (Arguments, WitnessValues, FinalTransaction); +pub trait FuzzFinalTransactionBuilder { + fn get_initial_ft(&self) -> FinalTransactionBuilder; } pub trait ProgramCheck { diff --git a/crates/test/src/fuzz/engine.rs b/crates/test/src/fuzz/engine.rs new file mode 100644 index 00000000..a92dc76d --- /dev/null +++ b/crates/test/src/fuzz/engine.rs @@ -0,0 +1,328 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::sync::Arc; + +use simplicityhl::{Arguments, WitnessValues}; + +use proptest::prelude::{BoxedStrategy, TestCaseError}; +use proptest::strategy::Strategy; +use proptest::test_runner::TestRunner; + +use smplx_sdk::program::{ArgumentsTrait, ProgramFactory, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; +use smplx_sdk::provider::{EsploraProvider, ProviderTrait, SimplicityNetwork}; +use smplx_sdk::signer::Signer; + +use crate::context::TestContext; +use crate::fuzz::args_strategy::{InterestingRandom, Random, RandomValuePool}; +use crate::fuzz::builders::FinalTransactionBuilder; +use crate::fuzz::core::{FuzzContext, FuzzFinalTransactionBuilder, FuzzableProgram, SignerOption}; +use crate::fuzz::{ProgramCheck, ProgramExecResult}; + +pub struct SimplexFuzzEngine { + runner: TestRunner, + fuzz_context: FuzzContext, + local_fuzz_config: LocalFuzzConfig, + strategy_storage: BoxedStrategy<(Arguments, WitnessValues)>, + blueprint: Box>, +} + +pub struct LocalFuzzConfig { + runs: usize, +} + +pub struct FuzzEngineBuilder { + proptest_config: proptest::test_runner::Config, + pub(crate) _strategy_storage: Option>, + pub(crate) _blueprint: Option>>, + pub(crate) signer_option: SignerOption, + pub(crate) signer: Option, + test_context: Option, + mock_provider: Arc, + network: SimplicityNetwork, + _phantom: PhantomData<(Program, Args, Wit)>, +} + +impl FuzzEngineBuilder +where + Program: FuzzableProgram + ProgramFactory + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, +{ + pub fn new(config: proptest::test_runner::Config) -> Self { + let default_network = SimplicityNetwork::default_regtest(); + + Self { + proptest_config: config, + _strategy_storage: None, + _blueprint: None, + test_context: None, + signer_option: SignerOption::NoSigning, + signer: None, + mock_provider: Arc::new(Self::get_default_provider(default_network)), + network: default_network, + _phantom: PhantomData, + } + } + + pub fn from_context(mut config: proptest::test_runner::Config, test_context: TestContext) -> Self { + let default_network = SimplicityNetwork::default_regtest(); + let smplx_test_context = dbg!(test_context.get_config()); + if let Some(proptest_conf) = smplx_test_context.proptest.as_ref() { + if let Some(cases) = proptest_conf.cases { + config.cases = cases; + } + + if let Some(max_global_rejects) = proptest_conf.max_global_rejects { + config.max_global_rejects = max_global_rejects; + } + + if let Some(max_shrink_iters) = proptest_conf.max_shrink_iters { + config.max_shrink_iters = max_shrink_iters; + } + + if let Some(max_local_rejects) = proptest_conf.max_local_rejects { + config.max_local_rejects = max_local_rejects; + } + + config.verbose = smplx_test_context.verbosity as u32; + } + + Self { + proptest_config: config, + _strategy_storage: None, + _blueprint: None, + test_context: None, + signer_option: SignerOption::DefaultTestConfigSigner, + signer: None, + mock_provider: Arc::new(Self::get_default_provider(default_network)), + network: default_network, + _phantom: PhantomData, + } + } + + fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { + EsploraProvider::new("default_web_page.com".into(), default_network) + } +} + +impl FuzzEngineBuilder +where + Program: FuzzableProgram + ProgramFactory + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, +{ + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer_option = SignerOption::CustomSigner; + self.signer = Some(signer); + self + } + + pub fn with_default_signer(mut self) -> Self { + self.signer_option = SignerOption::DefaultTestConfigSigner; + self + } + + pub fn with_no_signer(mut self) -> Self { + self.signer_option = SignerOption::NoSigning; + self + } +} + +impl FuzzEngineBuilder +where + Program: FuzzableProgram + ProgramFactory + Clone + 'static, + Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, + Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, +{ + pub fn build( + self, + strategy_storage: impl Strategy + 'static, + blueprint: impl FuzzFinalTransactionBuilder + 'static, + ) -> SimplexFuzzEngine { + // TODO: add fetching of failure values to feed correct seed into TestRunner on creation + + SimplexFuzzEngine { + runner: proptest::test_runner::TestRunner::new(self.proptest_config), + fuzz_context: FuzzContext { + #[allow(clippy::arc_with_non_send_sync)] + signer: Arc::new(self.signer), + mock_provider: self.mock_provider, + #[allow(clippy::arc_with_non_send_sync)] + test_context: Arc::new(self.test_context), + signer_option: self.signer_option, + network: self.network, + }, + local_fuzz_config: LocalFuzzConfig { runs: 500 }, + strategy_storage: strategy_storage.boxed(), + blueprint: Box::new(blueprint), + } + } +} + +pub struct FuzzStrategyBuilder { + base_strat: Option, + _placeholder: PhantomData<(Args, Wit)>, +} + +impl FuzzStrategyBuilder { + pub fn new() -> Self { + Self { + base_strat: None, + _placeholder: Default::default(), + } + } +} + +impl Default for FuzzStrategyBuilder { + fn default() -> Self { + Self::new() + } +} + +impl FuzzStrategyBuilder { + pub fn with_random(self) -> FuzzStrategyBuilder> { + FuzzStrategyBuilder { + base_strat: Some(Random::::default()), + _placeholder: Default::default(), + } + } + + pub fn with_random_pool(self) -> FuzzStrategyBuilder> { + FuzzStrategyBuilder { + base_strat: Some(RandomValuePool::::default()), + _placeholder: Default::default(), + } + } + + pub fn with_custom_strategy(self, custom_strat: NewStrat) -> FuzzStrategyBuilder + where + NewStrat: Strategy + 'static, + { + FuzzStrategyBuilder { + base_strat: Some(custom_strat), + _placeholder: Default::default(), + } + } + + pub fn with_random_interesting_values(self) -> FuzzStrategyBuilder> { + FuzzStrategyBuilder { + base_strat: Some(InterestingRandom::::default()), + _placeholder: Default::default(), + } + } +} + +impl FuzzStrategyBuilder +where + BaseStrat: Strategy + 'static, +{ + pub fn build(self) -> BoxedStrategy<(Arguments, WitnessValues)> { + let base = self + .base_strat + .expect("Base strategy is mandatory. Call with_random() or similar."); + base.boxed() + } +} + +// TODO: create a shared state with Atomic counter (for multiple runners) + +#[derive(Debug)] +pub struct CaseOutcome {} + +/// Returned by a single fuzz when a counterexample has been discovered +#[derive(Debug)] +pub struct CounterExampleOutcome { + pub args: Arguments, + pub wit: WitnessValues, +} + +/// Outcome of a single fuzz +#[derive(Debug)] +pub enum FuzzOutcome { + Case(CaseOutcome), + CounterExample(CounterExampleOutcome), +} + +impl SimplexFuzzEngine +where + Program: FuzzableProgram + ProgramFactory + Clone + 'static, +{ + pub fn run_with_check(self, program_post_hook: impl ProgramCheck) { + let mut runner = self.runner; + let context = self.fuzz_context; + let blueprint = self.blueprint; + let strategy_storage = self.strategy_storage; + + let fuzz_tx_blueprint = blueprint.get_initial_ft(); + + // TODO: add collected strategies usage to modify FinalTransaction or inputs into `proptest::prop_oneof[ strategy list ... ]` + + let mut runner_cnt = 0; + while runner_cnt < self.local_fuzz_config.runs { + // TODO: If counterexample recorded, replay it first, without incrementing runs. + // TODO: evaluate incremental runs + let mut inc_runs = || { + debug_assert!( + runner_cnt <= self.local_fuzz_config.runs, + "worker runs were not distributed correctly" + ); + runner_cnt += 1; + }; + + match Self::single_fuzz( + &mut runner, + &context, + fuzz_tx_blueprint.clone(), + &program_post_hook, + &strategy_storage, + ) { + Ok(fuzz_outcome) => match fuzz_outcome { + FuzzOutcome::Case(_case) => { + inc_runs(); + } + FuzzOutcome::CounterExample(CounterExampleOutcome { args, wit }) => { + panic!("program failed tith these arguments: {args} and witness: {wit}"); + } + }, + Err(err) => match err { + TestCaseError::Fail(e) => { + panic!("{e}"); + } + TestCaseError::Reject(e) => { + panic!("{e}"); + } + }, + } + } + } + + fn single_fuzz( + test_runner: &mut TestRunner, + fuzz_context: &FuzzContext, + initial_tx: FinalTransactionBuilder, + program_post_hook: &impl ProgramCheck, + strategy: &BoxedStrategy<(Arguments, WitnessValues)>, + ) -> Result { + // TODO: extract seed in order to save it in failure case + let (arguments, witness, final_transaction) = + initial_tx.insert_real_program_values::(test_runner, strategy); + let pst = fuzz_context.sign_or_extract(&final_transaction)?; + + let (failure_program, _script) = Program::build_program(arguments.clone(), &fuzz_context.network); + + // Iterate over program inputs to check contract execution + for (i, input) in final_transaction.inputs().iter().enumerate() { + if input.program_input.as_ref().is_some() { + let exec_result: ProgramExecResult = + failure_program + .as_ref() + .as_ref() + .execute(&pst, &witness, i, &fuzz_context.network); + if let Err(e) = program_post_hook.call(fuzz_context, &pst, &arguments, &witness, i, exec_result) { + return Err(TestCaseError::fail(format!("{e}, args: {arguments}, wit: {witness}"))); + } + } + } + Ok(FuzzOutcome::Case(CaseOutcome {})) + } +} diff --git a/crates/test/src/mutantesting/mod.rs b/crates/test/src/fuzz/mod.rs similarity index 71% rename from crates/test/src/mutantesting/mod.rs rename to crates/test/src/fuzz/mod.rs index 40c13db0..2c2a6af7 100644 --- a/crates/test/src/mutantesting/mod.rs +++ b/crates/test/src/fuzz/mod.rs @@ -1,4 +1,5 @@ pub mod args_strategy; +pub mod builders; pub mod core; pub mod engine; pub mod utils; @@ -6,5 +7,5 @@ pub mod utils; pub use proptest; pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; -pub use engine::{FuzzStrategyBuilder, SimplexFuzzEngine, get_default_provider}; +pub use engine::{FuzzEngineBuilder, SimplexFuzzEngine}; pub use utils::generate_value_by_ty; diff --git a/crates/test/src/mutantesting/utils.rs b/crates/test/src/fuzz/utils.rs similarity index 100% rename from crates/test/src/mutantesting/utils.rs rename to crates/test/src/fuzz/utils.rs index 762a693b..7214e37a 100644 --- a/crates/test/src/mutantesting/utils.rs +++ b/crates/test/src/fuzz/utils.rs @@ -1,10 +1,10 @@ -use rand::Rng; - use simplicityhl::num::U256; use simplicityhl::types::{TypeInner, UIntType}; use simplicityhl::value::ValueConstructible; use simplicityhl::{ResolvedType, Value}; +use rand::Rng; + pub fn generate_value_by_ty(ty: &ResolvedType, rng: &mut R) -> Value { match ty.as_inner() { TypeInner::Either(left_ty, right_ty) => match rng.random::() { diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index c68625ab..f3a2ab01 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -1,8 +1,8 @@ pub mod config; pub mod context; pub mod error; +pub mod fuzz; pub mod macros; -pub mod mutantesting; pub mod network_utils; pub use config::{RpcConfig, TEST_ENV_NAME, TestConfig}; diff --git a/crates/test/src/macros/core.rs b/crates/test/src/macros/core.rs index acd0083d..949de3eb 100644 --- a/crates/test/src/macros/core.rs +++ b/crates/test/src/macros/core.rs @@ -8,13 +8,13 @@ macro_rules! smplx_test_marker { () => { "_smplx_test" }; - (prop) => { - "_smplx_proptest" + (fuzz) => { + "_smplx_fuzz" }; } pub const SMPLX_TEST_MARKER: &str = smplx_test_marker!(); -pub const SMPLX_PROPTEST_MARKER: &str = smplx_test_marker!(prop); +pub const SMPLX_FUZZ_MARKER: &str = smplx_test_marker!(fuzz); type AttributeArgs = syn::punctuated::Punctuated; @@ -25,11 +25,11 @@ pub fn expand_test(args: TokenStream, input: syn::ItemFn) -> syn::Result syn::Result { +pub fn expand_fuzz(args: TokenStream, input: syn::ItemFn) -> syn::Result { let parser = AttributeArgs::parse_terminated; let args = parser.parse2(args)?; - expand_proptest_inner(&input, args) + expand_fuzz_inner(&input, args) } // TODO: args? @@ -70,9 +70,9 @@ fn expand_inner(input: &syn::ItemFn, _args: AttributeArgs) -> syn::Result syn::Result { +fn expand_fuzz_inner(input: &syn::ItemFn, _args: AttributeArgs) -> syn::Result { let ret = &input.sig.output; - let name = quote::format_ident!("{}_{}", &input.sig.ident.to_string(), SMPLX_PROPTEST_MARKER); + let name = quote::format_ident!("{}_{}", &input.sig.ident.to_string(), SMPLX_FUZZ_MARKER); let inputs = &input.sig.inputs; let body = &input.block; let attrs = &input.attrs; @@ -90,7 +90,7 @@ fn expand_proptest_inner(input: &syn::ItemFn, _args: AttributeArgs) -> syn::Resu #body } - let config = mutantesting::proptest::test_runner::Config { + let config = fuzz::proptest::test_runner::Config { test_name: ::core::option::Option::Some(::core::concat!( ::core::module_path!(), "::", @@ -112,7 +112,7 @@ fn expand_proptest_inner(input: &syn::ItemFn, _args: AttributeArgs) -> syn::Resu TestContext::new(PathBuf::from(path)).unwrap() } }; - let fuzz_context_builder = FuzzStrategyBuilder::from_context(config, test_context); + let fuzz_context_builder = FuzzEngineBuilder::from_context(config, test_context); #name(fuzz_context_builder) } }; diff --git a/crates/test/src/macros/mod.rs b/crates/test/src/macros/mod.rs index b643ff46..f266cc16 100644 --- a/crates/test/src/macros/mod.rs +++ b/crates/test/src/macros/mod.rs @@ -1,3 +1,3 @@ pub mod core; -pub use core::{expand_proptest, expand_test}; +pub use core::{expand_fuzz, expand_test}; diff --git a/crates/test/src/mutantesting/engine.rs b/crates/test/src/mutantesting/engine.rs deleted file mode 100644 index 58a8f560..00000000 --- a/crates/test/src/mutantesting/engine.rs +++ /dev/null @@ -1,433 +0,0 @@ -use std::fmt::Debug; -use std::marker::PhantomData; -use std::sync::Arc; - -use proptest::prelude::{BoxedStrategy, Just, TestCaseError}; -use proptest::strategy::Strategy; - -use simplicityhl::elements::hashes::Hash; -use simplicityhl::{Arguments, WitnessValues}; - -use smplx_sdk::program::{ArgumentsTrait, ProgramTrait, RandomArguments, RandomWitness, WitnessTrait}; -use smplx_sdk::provider::{EsploraProvider, ProviderTrait, SimplicityNetwork}; -use smplx_sdk::signer::Signer; -use smplx_sdk::transaction::{FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature, UTXO}; - -use crate::context::TestContext; -use crate::mutantesting::args_strategy::{Random, RandomValuePool}; -use crate::mutantesting::core::{ - ContractFuzzStrategy, ContractFuzzStrategyBlueprint, FuzzContext, FuzzableProgram, SignerOption, -}; -use crate::mutantesting::{ProgramCheck, ProgramExecResult}; - -pub struct SimplexFuzzEngine { - runner: proptest::test_runner::TestRunner, - fuzz_context: FuzzContext, - strategy_storage: BoxedStrategy<((Arguments, WitnessValues), AdditionalValue)>, - blueprint: Box>, -} - -pub fn get_default_provider(default_network: SimplicityNetwork) -> EsploraProvider { - EsploraProvider::new("default_web_page.com".into(), default_network) -} - -pub struct FuzzStrategyBuilder { - proptest_config: proptest::test_runner::Config, - pub(crate) _strategy_storage: Option>, - pub(crate) _blueprint: Option>>, - pub(crate) signer_option: SignerOption, - pub(crate) signer: Option, - test_context: Option, - mock_provider: Arc, - network: SimplicityNetwork, - _phantom: PhantomData<(Program, Args, Wit)>, -} - -impl FuzzStrategyBuilder -where - Program: FuzzableProgram + Clone + 'static, - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - Add: std::fmt::Debug + Clone + 'static, -{ - pub fn new(config: proptest::test_runner::Config) -> Self { - let default_network = SimplicityNetwork::default_regtest(); - - Self { - proptest_config: config, - _strategy_storage: None, - _blueprint: None, - test_context: None, - signer_option: SignerOption::NoSigning, - signer: None, - mock_provider: Arc::new(get_default_provider(default_network)), - network: default_network, - _phantom: PhantomData, - } - } - - pub fn from_context(mut config: proptest::test_runner::Config, test_context: TestContext) -> Self { - let default_network = SimplicityNetwork::default_regtest(); - let smplx_test_context = dbg!(test_context.get_config()); - if let Some(proptest_conf) = smplx_test_context.proptest.as_ref() { - if let Some(cases) = proptest_conf.cases { - config.cases = cases; - } - - if let Some(max_global_rejects) = proptest_conf.max_global_rejects { - config.max_global_rejects = max_global_rejects; - } - - if let Some(max_shrink_iters) = proptest_conf.max_shrink_iters { - config.max_shrink_iters = max_shrink_iters; - } - - if let Some(max_local_rejects) = proptest_conf.max_local_rejects { - config.max_local_rejects = max_local_rejects; - } - - config.verbose = smplx_test_context.verbosity as u32; - } - - Self { - proptest_config: config, - _strategy_storage: None, - _blueprint: None, - test_context: None, - signer_option: SignerOption::DefaultTestConfigSigner, - signer: None, - mock_provider: Arc::new(get_default_provider(default_network)), - network: default_network, - _phantom: PhantomData, - } - } -} - -impl FuzzStrategyBuilder -where - Program: FuzzableProgram + Clone + 'static, - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - AdditionalValue: Debug + Clone, -{ - pub fn with_signer(mut self, signer: Signer) -> Self { - self.signer_option = SignerOption::CustomSigner; - self.signer = Some(signer); - self - } - - pub fn with_default_signer(mut self) -> Self { - self.signer_option = SignerOption::DefaultTestConfigSigner; - self - } - - pub fn with_no_signer(mut self) -> Self { - self.signer_option = SignerOption::NoSigning; - self - } -} - -impl FuzzStrategyBuilder -where - Program: FuzzableProgram + Clone + 'static, - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - AdditionalValue: Debug + Clone, -{ - pub fn build( - self, - strategy_storage: impl Strategy + 'static, - blueprint: impl ContractFuzzStrategyBlueprint + 'static, - ) -> SimplexFuzzEngine { - SimplexFuzzEngine { - runner: proptest::test_runner::TestRunner::new(self.proptest_config), - fuzz_context: FuzzContext { - #[allow(clippy::arc_with_non_send_sync)] - signer: Arc::new(self.signer), - mock_provider: self.mock_provider, - #[allow(clippy::arc_with_non_send_sync)] - test_context: Arc::new(self.test_context), - signer_option: self.signer_option, - network: self.network, - }, - strategy_storage: strategy_storage.boxed(), - blueprint: Box::new(blueprint), - } - } -} - -pub type FuzzTxBluepirintStep = - dyn Fn(&mut FinalTransaction, &FuzzContext, &Arguments, &WitnessValues, &AdditionalValue) + Send + Sync + 'static; - -pub struct FuzzTxBlueprint { - pub(crate) steps: Vec>>, - _phantom: PhantomData<(Program, Args, Wit)>, -} - -impl ContractFuzzStrategyBlueprint for S -where - S: ContractFuzzStrategy + 'static, - Program: FuzzableProgram + Clone + 'static, - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - S::AdditionalInput: std::fmt::Debug + Clone + 'static, -{ - type AdditionalInput = S::AdditionalInput; - - fn get_final_tx( - &self, - context: &FuzzContext, - args: &Arguments, - wit: &WitnessValues, - additional: &Self::AdditionalInput, - ) -> FinalTransaction { - let (_, _, ft) = self.gen_final_transaction(context.clone(), args.clone(), wit.clone(), additional.clone()); - ft - } -} - -pub struct FnBlueprintWrapper { - f: F, - _phantom: PhantomData<(Args, Wit, Add)>, -} - -impl ContractFuzzStrategyBlueprint - for FnBlueprintWrapper -where - F: Fn(&FuzzContext, &Arguments, &WitnessValues, &AdditionalInput) -> FinalTransaction + 'static, - Program: FuzzableProgram + Clone + 'static, - Args: ArgumentsTrait + RandomArguments + std::fmt::Debug + Clone + 'static, - Wit: WitnessTrait + RandomWitness + std::fmt::Debug + Clone + 'static, - AdditionalInput: std::fmt::Debug + Clone + 'static, -{ - type AdditionalInput = AdditionalInput; - - fn get_final_tx( - &self, - context: &FuzzContext, - args: &Arguments, - wit: &WitnessValues, - additional: &Self::AdditionalInput, - ) -> FinalTransaction { - (self.f)(context, args, wit, additional) - } -} - -impl ContractFuzzStrategyBlueprint - for FuzzTxBlueprint -where - Program: FuzzableProgram + Clone + 'static, - Add: std::fmt::Debug + Clone + 'static, -{ - type AdditionalInput = Add; - - fn get_final_tx( - &self, - context: &FuzzContext, - args: &Arguments, - wit: &WitnessValues, - additional: &Self::AdditionalInput, - ) -> FinalTransaction { - let mut ft = FinalTransaction::new(); - for step in &self.steps { - step(&mut ft, context, args, wit, additional); - } - ft - } -} - -impl FuzzTxBlueprint -where - Program: FuzzableProgram + Clone + 'static, - Add: std::fmt::Debug + Clone + 'static, -{ - pub fn new() -> Self { - Self { - steps: Vec::new(), - _phantom: PhantomData, - } - } - - pub fn add_program_input(mut self, amount: u64) -> Self { - self.steps - .push(Arc::new(move |ft, context, arguments, witness, _additional| { - let (prog, script) = Program::build_program(arguments.clone(), &context.network); - let mut txout = simplicityhl::elements::TxOut::new_fee(amount, context.network.policy_asset()); - txout.script_pubkey = script; - - let partial_input = PartialInput::new(UTXO { - outpoint: simplicityhl::elements::OutPoint::new(simplicityhl::elements::Txid::all_zeros(), 0), - txout, - secrets: None, - }); - let program_input = ProgramInput::new(Box::new(prog.as_ref().as_ref().clone()), witness.clone()); - - ft.add_program_input(partial_input, program_input, RequiredSignature::None); - })); - self - } - - pub fn add_static_input(mut self, partial_input: PartialInput, required_sig: RequiredSignature) -> Self { - self.steps - .push(Arc::new(move |ft, _context, _arguments, _witness, _additional| { - ft.add_input(partial_input.clone(), required_sig.clone()); - })); - self - } - - pub fn add_static_output(mut self, partial_output: PartialOutput) -> Self { - self.steps - .push(Arc::new(move |ft, _context, _arguments, _witness, _additional| { - ft.add_output(partial_output.clone()); - })); - self - } - - pub fn add_custom_step(mut self, step: F) -> Self - where - F: Fn(&mut FinalTransaction, &FuzzContext, &Arguments, &WitnessValues, &Add) + Send + Sync + 'static, - { - self.steps.push(Arc::new(step)); - self - } -} - -pub struct StrategyStorageBuilder { - base_strat: Option, - add_strat: Option, - - _placeholder: PhantomData<(Args, Wit)>, -} - -impl Default for StrategyStorageBuilder> { - fn default() -> Self { - Self::new() - } -} - -impl StrategyStorageBuilder> { - pub fn new() -> Self { - Self { - base_strat: None, - add_strat: Some(Just(())), - _placeholder: Default::default(), - } - } -} - -impl Default for FuzzTxBlueprint -where - Program: FuzzableProgram + Clone + 'static, - Add: std::fmt::Debug + Clone + 'static, -{ - fn default() -> Self { - Self::new() - } -} - -impl StrategyStorageBuilder { - pub fn with_random(self) -> StrategyStorageBuilder, AddStrat> { - StrategyStorageBuilder { - base_strat: Some(Random::::default()), - add_strat: self.add_strat, - _placeholder: Default::default(), - } - } - - pub fn with_random_pool(self) -> StrategyStorageBuilder, AddStrat> { - StrategyStorageBuilder { - base_strat: Some(RandomValuePool::::default()), - add_strat: self.add_strat, - _placeholder: Default::default(), - } - } - - pub fn with_custom_strategy( - self, - custom_strat: NewStrat, - ) -> StrategyStorageBuilder - where - NewStrat: Strategy + 'static, - { - StrategyStorageBuilder { - base_strat: Some(custom_strat), - add_strat: self.add_strat, - _placeholder: Default::default(), - } - } - - pub fn with_additional_strategy( - self, - strategy: NewAddStrat, - ) -> StrategyStorageBuilder - where - NewAddStrat: Strategy + 'static, - { - StrategyStorageBuilder { - base_strat: self.base_strat, - add_strat: Some(strategy), - _placeholder: Default::default(), - } - } - - pub fn with_random_asset_value(self) -> StrategyStorageBuilder> { - StrategyStorageBuilder { - base_strat: self.base_strat, - add_strat: Some(0..u64::MAX), - _placeholder: Default::default(), - } - } -} - -impl StrategyStorageBuilder -where - BaseStrat: Strategy + 'static, - AddStrat: Strategy + 'static, -{ - pub fn build(self) -> BoxedStrategy<((Arguments, WitnessValues), AddStrat::Value)> { - let base = self - .base_strat - .expect("Base strategy is mandatory. Call with_random() or similar."); - let add = self.add_strat.expect("Additional strategy is missing."); - - (base, add).boxed() - } -} - -impl SimplexFuzzEngine -where - Program: FuzzableProgram + Clone + 'static, - AdditionalValue: Clone + Debug + 'static, -{ - pub fn run_with_check(self, program_post_hook: impl ProgramCheck) { - let mut runner = self.runner; - let context = self.fuzz_context; - let strategy = self.strategy_storage; - let blueprint = self.blueprint; - - match runner.run(&strategy, |((args, wit), add)| { - let ft = blueprint.get_final_tx(&context, &args, &wit, &add); - let pst = context.sign_or_extract(&ft).unwrap(); - - let (failure_program, _script) = Program::build_program(args.clone(), &context.network); - - // Iterate over program inputs to sign only appropriate items - for (i, input) in ft.inputs().iter().enumerate() { - if input.program_input.as_ref().is_some() { - let exec_result: ProgramExecResult = - failure_program - .as_ref() - .as_ref() - .execute(&pst, &wit, i, &context.network); - if let Err(e) = program_post_hook.call(&context, &pst, &args, &wit, i, exec_result) { - return Err(TestCaseError::fail(e)); - } - } - } - Ok(()) - }) { - Ok(()) => (), - Err(e) => ::core::panic!("{}\n{}", e, runner), - }; - } -} diff --git a/fixtures/tests/prop_testing.rs b/fixtures/tests/prop_testing.rs index 77bf4475..c2830ca9 100644 --- a/fixtures/tests/prop_testing.rs +++ b/fixtures/tests/prop_testing.rs @@ -1,14 +1,11 @@ mod failure_test_prop { - use crate::simple_storage_test_prop::SimpleStorageCheck; - use simplex::mutantesting; - use simplex::mutantesting::core::ContractFuzzStrategy; - use simplex::mutantesting::engine::StrategyStorageBuilder; - use simplex::mutantesting::{FuzzContext, FuzzStrategyBuilder, FuzzableProgram, ProgramCheck, ProgramExecResult}; - use simplex::simplicityhl::elements::hashes::Hash; + use simplex::fuzz; + use simplex::fuzz::builders::FinalTransactionBuilder; + use simplex::fuzz::core::{FuzzContext, FuzzFinalTransactionBuilder}; + use simplex::fuzz::engine::FuzzStrategyBuilder; + use simplex::fuzz::{FuzzEngineBuilder, ProgramCheck, ProgramExecResult}; use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; - use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; use simplex::simplicityhl::{Arguments, WitnessValues}; - use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature, UTXO}; use simplex_fixtures::artifacts::failure_test::FailureTestProgram; use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; @@ -27,11 +24,13 @@ mod failure_test_prop { let args = FailureTestArguments::from_arguments(_arguments)?; let witness = FailureTestWitness::from_witness(_witness)?; if args.failure_value == witness.cmp_value { - return Err(format!("Failed contract, {program_exec_result:?}")); + return Err(format!( + "Failed contract, failure_value == cmp_value , {program_exec_result:?}" + )); } if program_exec_result.is_err() { println!("error: {program_exec_result:?}"); - return Err(format!("Failed contract, {program_exec_result:?}")); + return Err(format!("Failed contract, error: {program_exec_result:?}")); } Ok(()) } @@ -40,47 +39,16 @@ mod failure_test_prop { #[derive(Debug, Default)] struct FailureGenStrategy; - impl ContractFuzzStrategy for FailureGenStrategy { - type AdditionalInput = (); - - fn gen_final_transaction( - &self, - test_context: FuzzContext, - arguments: Arguments, - witness: WitnessValues, - _additional: Self::AdditionalInput, - ) -> (Arguments, WitnessValues, FinalTransaction) { - const DEFAULT_FAUCET: u64 = 1 << 32; - - let mut ft = FinalTransaction::new(); - let (mutated_args, mutated_wit) = (arguments.clone(), witness.clone()); - - let (failure_program, failure_script) = FailureTestProgram::build_program(arguments, &test_context.network); - - let txout = { - let mut r = TxOut::new_fee(DEFAULT_FAUCET, test_context.network.policy_asset()); - r.script_pubkey = failure_script; - r - }; - - ft.add_program_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::all_zeros(), 0), - txout, - secrets: None, - }), - ProgramInput::new(Box::new(failure_program.as_ref().as_ref().clone()), witness), - RequiredSignature::None, - ); - - (mutated_args, mutated_wit, ft) + impl FuzzFinalTransactionBuilder for FailureGenStrategy { + fn get_initial_ft(&self) -> FinalTransactionBuilder { + FinalTransactionBuilder::new().add_program_input(None) } } #[ignore] #[test] fn possible_interface_failure_program() -> anyhow::Result<()> { - let config = mutantesting::proptest::test_runner::Config { + let config = fuzz::proptest::test_runner::Config { test_name: ::core::option::Option::Some(::core::concat!( ::core::module_path!(), "::", @@ -96,13 +64,13 @@ mod failure_test_prop { }; let fuzz_engine = - FuzzStrategyBuilder::::new(config); + FuzzEngineBuilder::::new(config); - let strategy_storage = StrategyStorageBuilder::::new() + let strategy_storage = FuzzStrategyBuilder::::new() .with_random() .build(); let runner = fuzz_engine.with_no_signer().build(strategy_storage, FailureGenStrategy); - runner.run_with_check(SimpleStorageCheck); + runner.run_with_check(FailureTestCheck); Ok(()) } @@ -110,7 +78,7 @@ mod failure_test_prop { #[ignore] #[test] fn possible_interface_failure_program_with_pool() -> anyhow::Result<()> { - let config = mutantesting::proptest::test_runner::Config { + let config = fuzz::proptest::test_runner::Config { test_name: ::core::option::Option::Some(::core::concat!( ::core::module_path!(), "::", @@ -125,298 +93,34 @@ mod failure_test_prop { ..Default::default() }; - let fuzz_engine = - FuzzStrategyBuilder::::new(config); + let fuzz_engine_builder = + FuzzEngineBuilder::::new(config); - let strategy_storage = StrategyStorageBuilder::::new() + // TODO: Add additional strategies to builder to make proper proptest + let strategy_storage = FuzzStrategyBuilder::::new() .with_random_pool() .build(); - let runner = fuzz_engine.with_no_signer().build(strategy_storage, FailureGenStrategy); - runner.run_with_check(SimpleStorageCheck); - - Ok(()) - } -} - -mod simple_storage_test_prop { - use simplex::mutantesting::core::ContractFuzzStrategy; - use simplex::mutantesting::engine::{StrategyStorageBuilder, get_default_provider}; - use simplex::mutantesting::{FuzzContext, FuzzStrategyBuilder, FuzzableProgram, ProgramCheck, ProgramExecResult}; - use simplex::program::{ArgumentsTrait, WitnessTrait}; - use simplex::provider::SimplicityNetwork; - use simplex::signer::Signer; - use simplex::simplicityhl::elements::AssetId; - use simplex::simplicityhl::elements::hashes::Hash; - use simplex::simplicityhl::elements::pset::PartiallySignedTransaction; - use simplex::simplicityhl::elements::pset::serialize::Serialize; - use simplex::simplicityhl::elements::{OutPoint, TxOut, Txid}; - use simplex::simplicityhl::{Arguments, WitnessValues}; - use simplex::transaction::PartialOutput; - use simplex::transaction::{FinalTransaction, PartialInput, RequiredSignature, UTXO}; - use simplex::{TestContext, mutantesting}; - use simplex_fixtures::artifacts::simple_storage::SimpleStorageProgram; - use simplex_fixtures::artifacts::simple_storage::derived_simple_storage::{ - SimpleStorageArguments, SimpleStorageWitness, - }; - use std::path::PathBuf; - - pub struct SimpleStorageCheck; - - impl ProgramCheck for SimpleStorageCheck { - fn call( - &self, - _ctx: &FuzzContext, - _tx: &PartiallySignedTransaction, - _arguments: &Arguments, - _witness: &WitnessValues, - _input_index: usize, - program_exec_result: ProgramExecResult, - ) -> Result<(), String> { - if let Err(x) = program_exec_result { - Err(format!("some error: {x:?}")) - } else { - Ok(()) - } - } - } - - #[derive(Debug, Default)] - struct SimpleStorageStrategy; - - impl ContractFuzzStrategy - for SimpleStorageStrategy - { - type AdditionalInput = u64; - - fn gen_final_transaction( - &self, - test_context: FuzzContext, - arguments: Arguments, - witness: WitnessValues, - additional: Self::AdditionalInput, - ) -> (Arguments, WitnessValues, FinalTransaction) { - let mut ft = FinalTransaction::new(); - let mut args_typed = SimpleStorageArguments::from_arguments(&arguments).unwrap(); - let mut wit_typed = SimpleStorageWitness::from_witness(&witness).unwrap(); - let signer = test_context.get_signer(); - let mut old_value = additional; - let new_value = if old_value == u64::MAX { - let res = old_value; - old_value -= 1; - res - } else { - old_value + 1 - }; - - wit_typed.new_value = new_value; - - { - let mut slot: [u8; 32] = Default::default(); - slot.copy_from_slice(&test_context.network.policy_asset().serialize()); - args_typed.slot_id = slot; - args_typed.user = signer.as_ref().unwrap().get_schnorr_public_key().serialize(); - } - let modified_args = args_typed.build_arguments(); - let (_fuzz_program, old_storage_args_script) = - SimpleStorageProgram::build_program(modified_args.clone(), &test_context.network); - - ft.add_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::from_slice(&[1; 32]).unwrap(), 0), - txout: { - let mut r = TxOut::new_fee(old_value, test_context.network.policy_asset()); - r.script_pubkey = old_storage_args_script.clone(); - r - }, - secrets: None, - }), - RequiredSignature::None, - ); - - ft.add_input( - PartialInput::new(UTXO { - outpoint: OutPoint::new(Txid::from_slice(&[2; 32]).unwrap(), 1), - txout: { - let mut r = TxOut::new_fee(1, AssetId::default()); - r.script_pubkey = old_storage_args_script.clone(); - r - }, - secrets: None, - }), - RequiredSignature::None, - ); - - ft.add_output(PartialOutput { - script_pubkey: old_storage_args_script.clone(), - amount: new_value, - asset: test_context.network.policy_asset(), - blinding_key: None, - }); - ft.add_output(PartialOutput { - script_pubkey: old_storage_args_script, - amount: 0, - asset: Default::default(), - blinding_key: None, - }); - - // TODO: how to make correctly here? - let pst = test_context.sign_or_extract(&ft).unwrap(); - let wit_signed = signer - .as_ref() - .unwrap() - .get_signed_program_witness( - &pst, - SimpleStorageProgram::new(modified_args.clone()).as_ref(), - &wit_typed.build_witness(), - "USER_SIGNATURE", - &[], - 0, - ) - .unwrap(); - - (modified_args, wit_signed, ft) - } - } - - #[ignore] - #[test] - fn possible_interface_simple_program() -> anyhow::Result<()> { - let config = mutantesting::proptest::test_runner::Config { - test_name: ::core::option::Option::Some(::core::concat!( - ::core::module_path!(), - "::", - ::core::stringify!(possible_interface_simple_program) - )), - source_file: Some(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/", - stringify!(possible_interface_simple_program), - ".txt" - )), - ..Default::default() - }; - - let fuzz_context_builder = FuzzStrategyBuilder::new(config); - - let strategy_storage = StrategyStorageBuilder::::new() - .with_random() - .with_random_asset_value() - .build(); - let runner = fuzz_context_builder - .with_signer({ - const DEFAULT_REGTEST_MNEMONIC: &str = - "exist carry drive collect lend cereal occur much tiger just involve mean"; - Signer::new( - DEFAULT_REGTEST_MNEMONIC, - Box::new(get_default_provider(SimplicityNetwork::default_regtest())), - ) - }) - .build(strategy_storage, SimpleStorageStrategy); - runner.run_with_check(SimpleStorageCheck); + let runner = fuzz_engine_builder + .with_no_signer() + .build(strategy_storage, FailureGenStrategy); + runner.run_with_check(FailureTestCheck); Ok(()) } - #[ignore] - #[test] - fn possible_interface_simple_program__smplx_test() -> anyhow::Result<()> { - fn possible_interface_simple_program__smplx_test( - builder: FuzzStrategyBuilder, - ) -> anyhow::Result<()> { - let strategy_storage = StrategyStorageBuilder::::new() - .with_random() - .with_random_asset_value() - .build(); - let runner = builder - .with_signer({ - const DEFAULT_REGTEST_MNEMONIC: &str = - "exist carry drive collect lend cereal occur much tiger just involve mean"; - Signer::new( - DEFAULT_REGTEST_MNEMONIC, - Box::new(get_default_provider(SimplicityNetwork::default_regtest())), - ) - }) - .build(strategy_storage, SimpleStorageStrategy); - runner.run_with_check(SimpleStorageCheck); - - Ok(()) - } - - const SIMPLEX_PROP_TEST_ENV: &str = "SIMPLEX_RUN_PROP_TESTS"; - - if std::env::var(SIMPLEX_PROP_TEST_ENV).is_ok() { - let test_context = match std::env::var("SIMPLEX_TEST_ENV") { - Err(_) => { - panic!("Failed to run this test, required to use `simplex test`"); - } - Ok(path) => TestContext::new(PathBuf::from(path)).unwrap(), - }; - - let config = mutantesting::proptest::test_runner::Config { - test_name: ::core::option::Option::Some(::core::concat!( - ::core::module_path!(), - "::", - ::core::stringify!(possible_interface_simple_program__smplx_test) - )), - source_file: Some(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/", - stringify!(possible_interface_simple_program__smplx_test), - ".txt" - )), - ..Default::default() - }; - let fuzz_context_builder = FuzzStrategyBuilder::from_context(config, test_context); - - possible_interface_simple_program__smplx_test(fuzz_context_builder) - } else { - eprintln!( - "Set '--proptest' flag in simplex to run a {} proptest", - stringify!(possible_interface_simple_program__smplx_test) - ); - - let config = mutantesting::proptest::test_runner::Config { - test_name: ::core::option::Option::Some(::core::concat!( - ::core::module_path!(), - "::", - ::core::stringify!(possible_interface_simple_program__smplx_test) - )), - source_file: Some(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/", - stringify!(possible_interface_simple_program__smplx_test), - ".txt" - )), - ..Default::default() - }; - let fuzz_context_builder = FuzzStrategyBuilder::new(config); - - possible_interface_simple_program__smplx_test(fuzz_context_builder) - - // todo!() - } - } - - #[simplex::proptest] - fn possible_interface_simple_program_2( - fuzz_engine: FuzzStrategyBuilder, + #[simplex::fuzz] + fn possible_interface_failure_program_with_interesting_values( + fuzz_engine_builder: FuzzEngineBuilder, ) -> anyhow::Result<()> { - let strategy_storage = StrategyStorageBuilder::::new() - .with_random_pool() - .with_random_asset_value() + // TODO: Add additional strategies to builder to make proper proptest + let strategy_storage = FuzzStrategyBuilder::::new() + .with_random_interesting_values() .build(); - // let blueprint = FuzzTxBlueprint::new().add_program_input().add_custom_step(); - let runner = fuzz_engine - .with_signer({ - const DEFAULT_REGTEST_MNEMONIC: &str = - "exist carry drive collect lend cereal occur much tiger just involve mean"; - Signer::new( - DEFAULT_REGTEST_MNEMONIC, - Box::new(get_default_provider(SimplicityNetwork::default_regtest())), - ) - }) - .build(strategy_storage, SimpleStorageStrategy); - runner.run_with_check(SimpleStorageCheck); + let runner = fuzz_engine_builder + .with_no_signer() + .build(strategy_storage, FailureGenStrategy); + runner.run_with_check(FailureTestCheck); + Ok(()) } } From 1c395df152419f2a1b3f93c8d12e283d9143267e Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 30 Jun 2026 14:31:39 +0300 Subject: [PATCH 19/21] fuzz_engine: edit function naming to reduce confusion --- crates/test/src/fuzz/builders/final_tx_builder.rs | 2 +- crates/test/src/fuzz/engine.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/test/src/fuzz/builders/final_tx_builder.rs b/crates/test/src/fuzz/builders/final_tx_builder.rs index a6fbecf0..528e663f 100644 --- a/crates/test/src/fuzz/builders/final_tx_builder.rs +++ b/crates/test/src/fuzz/builders/final_tx_builder.rs @@ -106,7 +106,7 @@ impl FinalTransactionBuilder { self } - pub fn get_inputs_to_check(&self) -> &[usize] { + pub fn get_input_idxs_to_check(&self) -> &[usize] { &self.meta.program_input_idxs } diff --git a/crates/test/src/fuzz/engine.rs b/crates/test/src/fuzz/engine.rs index a92dc76d..22c673ce 100644 --- a/crates/test/src/fuzz/engine.rs +++ b/crates/test/src/fuzz/engine.rs @@ -311,14 +311,16 @@ where let (failure_program, _script) = Program::build_program(arguments.clone(), &fuzz_context.network); // Iterate over program inputs to check contract execution - for (i, input) in final_transaction.inputs().iter().enumerate() { + for i in initial_tx.get_input_idxs_to_check() { + let input = &final_transaction.inputs()[*i]; + if input.program_input.as_ref().is_some() { let exec_result: ProgramExecResult = failure_program .as_ref() .as_ref() - .execute(&pst, &witness, i, &fuzz_context.network); - if let Err(e) = program_post_hook.call(fuzz_context, &pst, &arguments, &witness, i, exec_result) { + .execute(&pst, &witness, *i, &fuzz_context.network); + if let Err(e) = program_post_hook.call(fuzz_context, &pst, &arguments, &witness, *i, exec_result) { return Err(TestCaseError::fail(format!("{e}, args: {arguments}, wit: {witness}"))); } } From 3da255cdd27d78f97c91d30e80fb7e3a76dba8d8 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 30 Jun 2026 14:58:48 +0300 Subject: [PATCH 20/21] ui: fix tests imports --- crates/simplex/tests/ui/array_tr_storage.rs | 2 +- crates/simplex/tests/ui/bytes32_tr_storage.rs | 2 +- crates/simplex/tests/ui/dual_currency_deposit.rs | 2 +- crates/simplex/tests/ui/either_with_single_witness.rs | 2 +- crates/simplex/tests/ui/exotic_values.rs | 2 +- crates/simplex/tests/ui/list_check.rs | 2 +- crates/simplex/tests/ui/option_offer.rs | 2 +- crates/simplex/tests/ui/options.rs | 2 +- crates/simplex/tests/ui/simple_storage.rs | 2 +- crates/simplex/tests/ui/single_bit.rs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index 82583ba1..231dbfff 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs index 39ed805b..37bad410 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index b45c41ff..812d8a21 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index 2675db6a..548aa77f 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index a8e29767..c15584f5 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index 59f7bcfa..ea711b78 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index 997926a5..3e4435b0 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 2b2a03c4..296b64db 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index 298b2ba2..ae17addf 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index 091c5d87..aabb0ad6 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,5 +1,5 @@ use simplex::include_simf; -use simplex::mutantesting::{generate_value_by_ty}; +use simplex::fuzz::{generate_value_by_ty}; use simplex::program::Program; use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; use simplex::provider::SimplicityNetwork; From b2c4bfd44d7efe161cab5554f0c59186088b4822 Mon Sep 17 00:00:00 2001 From: Illia Kripaka Date: Tue, 30 Jun 2026 16:07:33 +0300 Subject: [PATCH 21/21] change test config title namings in Simple.toml examples --- crates/cli/assets/Simplex.default.toml | 2 +- examples/basic/Simplex.toml | 2 +- fixtures/Simplex.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cli/assets/Simplex.default.toml b/crates/cli/assets/Simplex.default.toml index fdc50134..1c9fca31 100644 --- a/crates/cli/assets/Simplex.default.toml +++ b/crates/cli/assets/Simplex.default.toml @@ -22,7 +22,7 @@ # url = "" # network = "" -# [test.proptest] +# [test.fuzz] # cases = 10000 # max_shrink_iters = 100 # max_global_rejects = 100 diff --git a/examples/basic/Simplex.toml b/examples/basic/Simplex.toml index deaeafca..f3ae2c0b 100644 --- a/examples/basic/Simplex.toml +++ b/examples/basic/Simplex.toml @@ -21,7 +21,7 @@ # bitcoins = 10_000_000 # verbosity = 0 -# [test.proptest] +# [test.fuzz] # cases = 10000 # max_shrink_iters = 100 # max_global_rejects = 100 diff --git a/fixtures/Simplex.toml b/fixtures/Simplex.toml index e5b3467c..f6cf9867 100644 --- a/fixtures/Simplex.toml +++ b/fixtures/Simplex.toml @@ -24,7 +24,7 @@ test_crate = { git = "https://github.com/LesterEvSe/test-simplex-crate" } # bitcoins = 10_000_000 # verbosity = 0 -# [test.proptest] +# [test.fuzz] # cases = 10000 # max_shrink_iters = 100 # max_global_rejects = 100