diff --git a/Cargo.lock b/Cargo.lock index 1123bfd7..ccc44cfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,12 +150,27 @@ 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", ] +[[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" @@ -1065,8 +1120,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 +1141,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 +1163,24 @@ 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 = "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" @@ -1204,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" @@ -1220,7 +1325,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 +1346,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", @@ -1387,7 +1492,6 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "smplx-build" version = "0.0.7" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", @@ -1454,6 +1558,7 @@ dependencies = [ "elements-miniscript", "hex", "minreq 3.0.0", + "rand_core 0.9.5", "serde", "serde_json", "sha2", @@ -1466,6 +1571,8 @@ name = "smplx-std" version = "0.0.7" dependencies = [ "either", + "rand 0.9.4", + "rand_core 0.9.5", "serde", "simplicityhl", "smplx-macros", @@ -1480,7 +1587,9 @@ version = "0.0.7" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", + "rand 0.9.4", "serde", "simplicityhl", "smplx-regtest", @@ -1686,6 +1795,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" @@ -1731,6 +1846,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/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..d043d2c7 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -19,5 +19,4 @@ 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"} +globwalk = { version = "0.9.1" } diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index 8b642382..5967d4b3 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()), } } @@ -283,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 2936e9be..923b6a91 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 GeneratedProgramTraitHelperTokens { + 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,41 @@ 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_program_trait_helpers_impl(&self) -> syn::Result { + let args_struct_name = &self.args_struct.struct_name; + let program_name = &self.program_struct_name; + + 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(GeneratedProgramTraitHelperTokens { + imports: quote! { + use super::{super::#program_name, #args_struct_name}; + use simplex::program::{Program, ProgramFactory}; + use simplex::simplicityhl::{Arguments}; + }, + helper_impls: quote! { + #program_helpers_impl + }, + }) + } } impl WitnessField { @@ -105,6 +137,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,28 +150,34 @@ 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 }, 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 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 { /// Build Simplicity arguments for contract instantiation. - #[must_use] fn build_arguments(&self) -> simplex::simplicityhl::Arguments { simplex::simplicityhl::Arguments::from(HashMap::from([ #(#tuples),* @@ -165,11 +204,24 @@ impl WitnessStruct { } } + impl simplex::program::RandomArguments for #struct_name { + fn generate_arguments(rng: &mut dyn RngCore) -> simplex::simplicityhl::Arguments + { + Self::generate_arguments_raw(rng).build_arguments() + } + } + impl core::default::Default for #struct_name { fn default() -> Self { #default_mapping } } + + impl From<#struct_name> for simplex::simplicityhl::Arguments { + fn from(val: #struct_name) -> simplex::simplicityhl::Arguments { + val.build_arguments() + } + } }, }) } @@ -187,6 +239,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,13 +251,15 @@ 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 }, struct_impl: quote! { impl #struct_name { - /// Build struct from Simplicity WitnessValues. + /// Build struct from Simplicity `WitnessValues`. /// /// # Errors /// @@ -214,11 +269,16 @@ 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 { /// Build Simplicity witness values for contract execution. - #[must_use] fn build_witness(&self) -> simplex::simplicityhl::WitnessValues { simplex::simplicityhl::WitnessValues::from(HashMap::from([ #(#tuples),* @@ -245,11 +305,24 @@ 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 } } + + impl From<#struct_name> for simplex::simplicityhl::WitnessValues { + fn from(val: #struct_name) -> simplex::simplicityhl::WitnessValues { + val.build_witness() + } + } }, }) } @@ -300,6 +373,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 @@ -367,6 +458,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..e50d29b2 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, GeneratedProgramTraitHelperTokens, 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_program_trait_helpers(derived_meta: &SimfContractMeta) -> syn::Result { + let GeneratedProgramTraitHelperTokens { imports, helper_impls } = + derived_meta.generate_program_trait_helpers_impl()?; + + Ok(quote! { + mod program_helpers { + #imports + + #helper_impls + } + }) +} diff --git a/crates/build/src/macros/types.rs b/crates/build/src/macros/types.rs index 00fbcb68..8bca1f8e 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::() & 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) => { + 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/cli/assets/Simplex.default.toml b/crates/cli/assets/Simplex.default.toml index e8eff361..1c9fca31 100644 --- a/crates/cli/assets/Simplex.default.toml +++ b/crates/cli/assets/Simplex.default.toml @@ -22,6 +22,12 @@ # url = "" # network = "" +# [test.fuzz] +# cases = 10000 +# max_shrink_iters = 100 +# max_global_rejects = 100 +# max_local_rejects = 100 + # [test.rpc] # url = "" # username = "" diff --git a/crates/cli/src/commands/core.rs b/crates/cli/src/commands/core.rs index 0927774a..5735240f 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, + /// 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 bb624526..ea9225b3 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_FUZZ_DSL_TEST_MARKER: &str = concat!("test(/", smplx_test_marker!(fuzz), "$/)"); 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.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 15136045..6842b40f 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 fuzz(args: TokenStream, input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::ItemFn); + + 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/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/core.rs b/crates/sdk/src/program/core.rs index d683f359..5e50d0cb 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; @@ -84,7 +83,7 @@ pub trait ProgramTrait: DynClone { pub struct Program { source: &'static str, pub_key: XOnlyPublicKey, - arguments: Box, + arguments: Arc, storage: Vec<[u8; 32]>, } @@ -196,14 +195,59 @@ 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] - 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 +351,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), ) @@ -374,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::{ @@ -383,6 +439,8 @@ mod tests { use super::*; + use crate::program::ArgumentsTrait; + // simplicityhl/examples/cat.simf const DUMMY_PROGRAM: &str = r" fn main() { @@ -404,12 +462,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 8722cf7e..2246c34a 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 core::{Program, ProgramTrait}; +pub use arguments::{ArgumentsTrait, RandomArguments}; +pub use core::{Program, ProgramFactory, 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/sdk/src/signer/core.rs b/crates/sdk/src/signer/core.rs index dc53b4d2..f455f055 100644 --- a/crates/sdk/src/signer/core.rs +++ b/crates/sdk/src/signer/core.rs @@ -490,6 +490,19 @@ 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`. + /// + /// # 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(); @@ -511,13 +524,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 = @@ -535,11 +548,16 @@ 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. + /// + /// # 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, program: &dyn ProgramTrait, @@ -559,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/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 88b6235c..f16502db 100644 --- a/crates/sdk/src/transaction/partial_input.rs +++ b/crates/sdk/src/transaction/partial_input.rs @@ -1,9 +1,12 @@ +use std::fmt::{Debug, Formatter}; +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 +66,13 @@ 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, +} + +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. @@ -168,8 +177,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/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..5b7f598a 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; @@ -6,6 +8,7 @@ pub use smplx_sdk::*; pub use smplx_test::config::TestConfig; pub use smplx_test::context::TestContext; +pub use smplx_test::fuzz; pub use smplx_macros; -pub use smplx_macros::{include_simf, test}; +pub use smplx_macros::{fuzz, include_simf, test}; diff --git a/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs index 26ad3d9a..231dbfff 100644 --- a/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{ArgumentsTrait, WitnessTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_array_tr_storage::ArrayTrStorageWitness::default(); let witness_values = original_witness.build_witness(); @@ -18,3 +92,88 @@ fn main() -> Result<(), String> { 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}; + + 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(()) +} + +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 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)?; + } + } + 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) +} \ 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 de30a3f4..37bad410 100644 --- a/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_bytes32_tr_storage::Bytes32TrStorageWitness::default(); let witness_values = original_witness.build_witness(); @@ -18,3 +92,90 @@ fn main() -> Result<(), String> { 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}; + + 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(()) +} + +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 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)?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs index a1395759..812d8a21 100644 --- a/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_dual_currency_deposit::DualCurrencyDepositWitness::default(); let witness_values = original_witness.build_witness(); @@ -19,3 +93,94 @@ fn main() -> Result<(), String> { 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); + + 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(()) +} + +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 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)?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/either_with_single_witness.rs b/crates/simplex/tests/ui/either_with_single_witness.rs index 9f1186ef..548aa77f 100644 --- a/crates/simplex/tests/ui/either_with_single_witness.rs +++ b/crates/simplex/tests/ui/either_with_single_witness.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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 witness_values = original_witness.build_witness(); @@ -20,3 +94,97 @@ fn main() -> Result<(), String> { 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); + + 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(()) +} + +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 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, + )?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/exotic_values.rs b/crates/simplex/tests/ui/exotic_values.rs index e0aab6a7..c15584f5 100644 --- a/crates/simplex/tests/ui/exotic_values.rs +++ b/crates/simplex/tests/ui/exotic_values.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_exotic_values::ExoticValuesWitness::default(); let witness_values = original_witness.build_witness(); @@ -13,9 +87,92 @@ fn main() -> 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(()) } + +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}; + + 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(()) +} + +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 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)?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/list_check.rs b/crates/simplex/tests/ui/list_check.rs index a36079a0..ea711b78 100644 --- a/crates/simplex/tests/ui/list_check.rs +++ b/crates/simplex/tests/ui/list_check.rs @@ -1,9 +1,85 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_behaviour()?; + let _ = test_build_panic()?; + 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_list_check::ListCheckWitness { draft: vec![], path: simplex::either::Left(vec![ @@ -17,12 +93,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 +117,99 @@ 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}; + + 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(()) +} + +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 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)?; + } + } 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) +} diff --git a/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs index 3ace0aeb..3e4435b0 100644 --- a/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_option_offer::OptionOfferWitness::default(); let witness_values = original_witness.build_witness(); @@ -18,3 +92,87 @@ fn main() -> Result<(), String> { 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 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(()) +} + +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 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)?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs index 9dad90af..296b64db 100644 --- a/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_options::OptionsWitness::default(); let witness_values = original_witness.build_witness(); @@ -18,3 +92,87 @@ fn main() -> Result<(), String> { 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}; + + 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(()) +} + +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 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)?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs index 34147700..ae17addf 100644 --- a/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,9 +1,83 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { + let _ = test_e2e_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_simple_storage::SimpleStorageWitness::default(); let witness_values = original_witness.build_witness(); @@ -18,3 +92,87 @@ fn main() -> Result<(), String> { 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 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(()) +} + +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 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)?; + } + } + 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) +} diff --git a/crates/simplex/tests/ui/single_bit.rs b/crates/simplex/tests/ui/single_bit.rs index d1397cc2..aabb0ad6 100644 --- a/crates/simplex/tests/ui/single_bit.rs +++ b/crates/simplex/tests/ui/single_bit.rs @@ -1,84 +1,180 @@ use simplex::include_simf; -use simplex::program::{WitnessTrait, ArgumentsTrait}; +use simplex::fuzz::{generate_value_by_ty}; +use simplex::program::Program; +use simplex::program::{ArgumentsTrait, RandomArguments, RandomWitness, WitnessTrait}; +use simplex::provider::SimplicityNetwork; +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, +} +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> { - // 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 _ = test_e2e_random_value_generation_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)" - ); + Ok(()) +} - let original_arguments = derived_single_bit::SingleBitArguments { flag: 1 }; +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 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)" - ); + 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) - let original_witness = derived_single_bit::SingleBitWitness { bit: 1 }; + let original_arguments = derived_single_bit::SingleBitArguments { flag }; - 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 arguments_values = original_arguments.build_arguments(); + let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; + assert_eq!(original_arguments, recovered_arguments); + } - let original_arguments = derived_single_bit::SingleBitArguments { flag: 0 }; + Ok(()) +} - let arguments_values = original_arguments.build_arguments(); - let recovered_arguments = derived_single_bit::SingleBitArguments::from_arguments(&arguments_values)?; +fn test_default() -> Result<(), String> { assert_eq!( - original_arguments, recovered_arguments, - "Testing values with (FLAG = 0, BIT = 1) (Arguments)" + derived_single_bit::SingleBitWitness::default(), + derived_single_bit::SingleBitWitness::default() ); - - // Testing values with (FLAG = 1, BIT = 0) - let original_witness = derived_single_bit::SingleBitWitness { bit: 0 }; - - 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)" + derived_single_bit::SingleBitArguments::default(), + derived_single_bit::SingleBitArguments::default() ); + Ok(()) +} - let original_arguments = derived_single_bit::SingleBitArguments { flag: 1 }; +fn test_e2e_random_behaviour() -> Result<(), String> { + for seed in 0..32 { + use simplex::rand::{rngs::StdRng, SeedableRng}; - 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 mut rng = StdRng::seed_from_u64(seed); - // Testing values with (FLAG = 0, BIT = 0) - let original_witness = derived_single_bit::SingleBitWitness { bit: 0 }; + 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, - "Testing values with (FLAG = 0, 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: 0 }; + 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); - 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)" - ); + 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(()) } + +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 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)?; + } + } + 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) +} diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index f5f22969..3e1d5f03 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -19,7 +19,10 @@ 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"] } quote = { version = "1.0.44" } +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/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/fuzz/args_strategy/random_pool.rs b/crates/test/src/fuzz/args_strategy/random_pool.rs new file mode 100644 index 00000000..42281f9a --- /dev/null +++ b/crates/test/src/fuzz/args_strategy/random_pool.rs @@ -0,0 +1,154 @@ +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 smplx_sdk::program::{RandomArguments, RandomWitness}; + +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 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/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..528e663f --- /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_input_idxs_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/fuzz/core.rs b/crates/test/src/fuzz/core.rs new file mode 100644 index 00000000..321712ca --- /dev/null +++ b/crates/test/src/fuzz/core.rs @@ -0,0 +1,88 @@ +use std::sync::Arc; + +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::provider::{ProviderTrait, SimplicityNetwork}; +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 { + DefaultTestConfigSigner, + CustomSigner, + NoSigning, +} + +#[derive(Clone)] +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, +} + +pub type ProgramExecResult = Result<(Arc, Value), ProgramError>; + +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) + } +} + +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 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 FuzzFinalTransactionBuilder { + fn get_initial_ft(&self) -> FinalTransactionBuilder; +} + +pub trait ProgramCheck { + fn call( + &self, + ctx: &FuzzContext, + tx: &PartiallySignedTransaction, + arguments: &Arguments, + witness: &WitnessValues, + input_index: usize, + program_exec_result: ProgramExecResult, + ) -> Result<(), String>; +} diff --git a/crates/test/src/fuzz/engine.rs b/crates/test/src/fuzz/engine.rs new file mode 100644 index 00000000..22c673ce --- /dev/null +++ b/crates/test/src/fuzz/engine.rs @@ -0,0 +1,330 @@ +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 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) { + return Err(TestCaseError::fail(format!("{e}, args: {arguments}, wit: {witness}"))); + } + } + } + Ok(FuzzOutcome::Case(CaseOutcome {})) + } +} diff --git a/crates/test/src/fuzz/mod.rs b/crates/test/src/fuzz/mod.rs new file mode 100644 index 00000000..2c2a6af7 --- /dev/null +++ b/crates/test/src/fuzz/mod.rs @@ -0,0 +1,11 @@ +pub mod args_strategy; +pub mod builders; +pub mod core; +pub mod engine; +pub mod utils; + +pub use proptest; + +pub use core::{FuzzContext, FuzzableProgram, ProgramCheck, ProgramExecResult}; +pub use engine::{FuzzEngineBuilder, SimplexFuzzEngine}; +pub use utils::generate_value_by_ty; diff --git a/crates/test/src/fuzz/utils.rs b/crates/test/src/fuzz/utils.rs new file mode 100644 index 00000000..7214e37a --- /dev/null +++ b/crates/test/src/fuzz/utils.rs @@ -0,0 +1,45 @@ +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::() { + 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(), + } +} diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 2725aa8a..f3a2ab01 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -1,6 +1,7 @@ pub mod config; pub mod context; pub mod error; +pub mod fuzz; pub mod macros; pub mod network_utils; diff --git a/crates/test/src/macros/core.rs b/crates/test/src/macros/core.rs index c6ecbbaf..949de3eb 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" }; + (fuzz) => { + "_smplx_fuzz" + }; } pub const SMPLX_TEST_MARKER: &str = smplx_test_marker!(); +pub const SMPLX_FUZZ_MARKER: &str = smplx_test_marker!(fuzz); 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_fuzz(args: TokenStream, input: syn::ItemFn) -> syn::Result { + let parser = AttributeArgs::parse_terminated; + let args = parser.parse2(args)?; + + expand_fuzz_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_FUZZ_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 = fuzz::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 = FuzzEngineBuilder::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..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; +pub use core::{expand_fuzz, expand_test}; diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 57022745..32d38de3 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -150,12 +150,27 @@ 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", ] +[[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" @@ -604,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" @@ -876,6 +891,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 +967,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 +996,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" @@ -981,8 +1030,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 +1051,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 +1073,24 @@ 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 = "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" @@ -1120,6 +1207,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" @@ -1136,7 +1235,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 +1256,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", @@ -1311,7 +1410,6 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "smplx-build" version = "0.0.7" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", @@ -1358,6 +1456,7 @@ dependencies = [ "elements-miniscript", "hex", "minreq 3.0.0", + "rand_core 0.9.5", "serde", "serde_json", "sha2", @@ -1370,6 +1469,8 @@ name = "smplx-std" version = "0.0.7" dependencies = [ "either", + "rand 0.9.4", + "rand_core 0.9.5", "serde", "simplicityhl", "smplx-macros", @@ -1383,7 +1484,9 @@ version = "0.0.7" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", + "rand 0.9.4", "serde", "simplicityhl", "smplx-regtest", @@ -1522,6 +1625,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" @@ -1567,6 +1676,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/Simplex.toml b/examples/basic/Simplex.toml index c522f64c..f3ae2c0b 100644 --- a/examples/basic/Simplex.toml +++ b/examples/basic/Simplex.toml @@ -21,6 +21,12 @@ # bitcoins = 10_000_000 # verbosity = 0 +# [test.fuzz] +# cases = 10000 +# max_shrink_iters = 100 +# max_global_rejects = 100 +# max_local_rejects = 100 + # [test.esplora] # url = "" # network = "" 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 d9de5994..71c37f1b 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -150,12 +150,27 @@ 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", ] +[[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" @@ -604,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" @@ -876,6 +891,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 +967,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 +996,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" @@ -981,8 +1030,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 +1051,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 +1073,24 @@ 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 = "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" @@ -1120,6 +1207,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" @@ -1136,7 +1235,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 +1256,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", @@ -1311,7 +1410,6 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" name = "smplx-build" version = "0.0.7" dependencies = [ - "glob", "globwalk", "pathdiff", "prettyplease", @@ -1358,6 +1456,7 @@ dependencies = [ "elements-miniscript", "hex", "minreq 3.0.0", + "rand_core 0.9.5", "serde", "serde_json", "sha2", @@ -1370,6 +1469,8 @@ name = "smplx-std" version = "0.0.7" dependencies = [ "either", + "rand 0.9.4", + "rand_core 0.9.5", "serde", "simplicityhl", "smplx-macros", @@ -1383,7 +1484,9 @@ version = "0.0.7" dependencies = [ "electrsd", "proc-macro2", + "proptest", "quote", + "rand 0.9.4", "serde", "simplicityhl", "smplx-regtest", @@ -1522,6 +1625,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" @@ -1567,6 +1676,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/Simplex.toml b/fixtures/Simplex.toml index 041dc506..f6cf9867 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.fuzz] +# cases = 10000 +# max_shrink_iters = 100 +# max_global_rejects = 100 +# max_local_rejects = 100 + # [test.esplora] # url = "" # network = "" 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/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); +} 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..c2830ca9 --- /dev/null +++ b/fixtures/tests/prop_testing.rs @@ -0,0 +1,126 @@ +mod failure_test_prop { + 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::{Arguments, WitnessValues}; + use simplex_fixtures::artifacts::failure_test::FailureTestProgram; + use simplex_fixtures::artifacts::failure_test::derived_failure_test::{FailureTestArguments, FailureTestWitness}; + + struct FailureTestCheck; + + impl ProgramCheck for FailureTestCheck { + fn call( + &self, + _ctx: &FuzzContext, + _tx: &PartiallySignedTransaction, + _arguments: &Arguments, + _witness: &WitnessValues, + _input_index: usize, + 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, failure_value == cmp_value , {program_exec_result:?}" + )); + } + if program_exec_result.is_err() { + println!("error: {program_exec_result:?}"); + return Err(format!("Failed contract, error: {program_exec_result:?}")); + } + Ok(()) + } + } + + #[derive(Debug, Default)] + struct FailureGenStrategy; + + 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 = fuzz::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 = + FuzzEngineBuilder::::new(config); + + let strategy_storage = FuzzStrategyBuilder::::new() + .with_random() + .build(); + let runner = fuzz_engine.with_no_signer().build(strategy_storage, FailureGenStrategy); + runner.run_with_check(FailureTestCheck); + + Ok(()) + } + + #[ignore] + #[test] + fn possible_interface_failure_program_with_pool() -> anyhow::Result<()> { + let config = fuzz::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_builder = + FuzzEngineBuilder::::new(config); + + // TODO: Add additional strategies to builder to make proper proptest + let strategy_storage = FuzzStrategyBuilder::::new() + .with_random_pool() + .build(); + let runner = fuzz_engine_builder + .with_no_signer() + .build(strategy_storage, FailureGenStrategy); + runner.run_with_check(FailureTestCheck); + + Ok(()) + } + + #[simplex::fuzz] + fn possible_interface_failure_program_with_interesting_values( + fuzz_engine_builder: FuzzEngineBuilder, + ) -> anyhow::Result<()> { + // TODO: Add additional strategies to builder to make proper proptest + let strategy_storage = FuzzStrategyBuilder::::new() + .with_random_interesting_values() + .build(); + let runner = fuzz_engine_builder + .with_no_signer() + .build(strategy_storage, FailureGenStrategy); + runner.run_with_check(FailureTestCheck); + + Ok(()) + } +}