diff --git a/simplex/Cargo.lock b/Cargo.lock similarity index 97% rename from simplex/Cargo.lock rename to Cargo.lock index 951ef2f..eeac8d4 100644 --- a/simplex/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -306,6 +306,16 @@ dependencies = [ "objc2", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -391,7 +401,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -424,6 +434,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -490,7 +519,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -702,6 +731,30 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.11.0", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -973,6 +1026,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.14", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -1288,31 +1357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "syn", ] [[package]] @@ -1662,6 +1707,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "santiago" version = "1.3.1" @@ -1758,7 +1812,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1847,10 +1901,11 @@ dependencies = [ "ctrlc", "dotenvy", "electrsd", - "glob", + "globwalk", "serde", "simplex-macros-core", "simplex-sdk", + "simplex-template-gen-core", "simplex-test", "simplicityhl", "thiserror", @@ -1867,23 +1922,19 @@ version = "0.1.0" dependencies = [ "serde", "simplex-macros-core", - "syn 2.0.117", + "syn", ] [[package]] name = "simplex-macros-core" version = "0.1.0" dependencies = [ - "pathdiff", - "prettyplease", - "proc-macro-error", "proc-macro2", "quote", "serde", "simplex-test", "simplicityhl", - "syn 2.0.117", - "thiserror", + "syn", ] [[package]] @@ -1919,6 +1970,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "simplex-template-gen-core" +version = "0.1.0" +dependencies = [ + "pathdiff", + "prettyplease", + "proc-macro2", + "quote", + "serde", + "simplex-macros-core", + "syn", + "thiserror", +] + [[package]] name = "simplex-test" version = "0.1.0" @@ -1962,7 +2027,7 @@ dependencies = [ [[package]] name = "simplicityhl" version = "0.4.1" -source = "git+https://github.com/ikripaka/SimplicityHL/?branch=feature%2Frich-params#77755254414093ff0e1b51beedbc27367745388e" +source = "git+https://github.com/BlockstreamResearch/SimplicityHL.git?rev=568b462#568b4621d6145cd97dce68a3f3428c7eb85306b6" dependencies = [ "base64 0.21.7", "chumsky", @@ -2029,16 +2094,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -2067,7 +2122,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2115,7 +2170,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2206,7 +2261,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2358,7 +2413,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2514,6 +2569,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2593,7 +2658,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -2903,7 +2968,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2919,7 +2984,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2986,7 +3051,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3007,7 +3072,7 @@ checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3027,7 +3092,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3067,7 +3132,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/simplex/Cargo.toml b/Cargo.toml similarity index 85% rename from simplex/Cargo.toml rename to Cargo.toml index edc4306..2d1a44e 100644 --- a/simplex/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "3" members = [ "crates/*", ] +exclude = ["examples/basic"] [workspace.package] license = "MIT OR Apache-2.0" @@ -14,6 +15,7 @@ multiple_crate_versions = "allow" [workspace.dependencies] simplex-provider = { path = "./crates/provider" } simplex-macros-core = { path = "./crates/macros-core", features = ["bincode", "serde"] } +simplex-template-gen-core = { path = "./crates/template-gen" } simplex-macros = { path = "./crates/macros" } simplex-test = { path = "./crates/test" } simplex-sdk = { path = "./crates/sdk" } @@ -33,7 +35,7 @@ tracing = { version = "0.1.41" } minreq = { version = "2.14.1", features = ["https", "json-using-serde"] } electrsd = { version = "0.29.0", features = ["legacy"] } -simplicityhl = { git = "https://github.com/ikripaka/SimplicityHL/", branch = "feature/rich-params" } +simplicityhl = { git = "https://github.com/BlockstreamResearch/SimplicityHL.git", rev = "568b462" } [patch.crates-io] simplicity-sys = { git = "https://github.com/BlockstreamResearch/rust-simplicity", tag = "simplicity-sys-0.6.1" } diff --git a/README.md b/README.md index e970c91..8e93f93 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,18 @@ This collection of useful crates provides useful utilities for working with Simp - `simplex-core` - provides useful utilities. - `simplex-cli` - provides common cli interface and ability to setup your contract development environment. +## Quick start + +Install simplex with command: +```bash +cargo install --path ./crates/cli +``` + +To run tests for this crate - run +```bash +RUN_SLOW_TESTS=true cargo test +``` + ## License Dual-licensed under either of: diff --git a/simplex/assets/elementsd b/assets/elementsd similarity index 100% rename from simplex/assets/elementsd rename to assets/elementsd diff --git a/simplex/crates/cli/Cargo.toml b/crates/cli/Cargo.toml similarity index 92% rename from simplex/crates/cli/Cargo.toml rename to crates/cli/Cargo.toml index 4e6624b..0d00017 100644 --- a/simplex/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,6 +18,7 @@ workspace = true simplex-test = { workspace = true } simplex-sdk = { workspace = true } simplex-macros-core = { workspace = true } +simplex-template-gen-core = { workspace = true } simplicityhl = { workspace = true } electrsd = { workspace = true } @@ -32,7 +33,7 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] } tracing = { version = "0.1.44" } tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } ctrlc = { version = "3.5.2", features = ["termination"] } -glob = { version = "0.3.3"} +globwalk = { version = "0.9.1"} [dev-dependencies] tracing = { workspace = true } diff --git a/simplex/crates/cli/Simplex.example.toml b/crates/cli/Simplex.example.toml similarity index 100% rename from simplex/crates/cli/Simplex.example.toml rename to crates/cli/Simplex.example.toml diff --git a/simplex/crates/cli/src/bin/main.rs b/crates/cli/src/bin/main.rs similarity index 100% rename from simplex/crates/cli/src/bin/main.rs rename to crates/cli/src/bin/main.rs diff --git a/simplex/crates/cli/src/cache_storage.rs b/crates/cli/src/cache_storage.rs similarity index 100% rename from simplex/crates/cli/src/cache_storage.rs rename to crates/cli/src/cache_storage.rs diff --git a/simplex/crates/cli/src/cli/commands.rs b/crates/cli/src/cli/commands.rs similarity index 56% rename from simplex/crates/cli/src/cli/commands.rs rename to crates/cli/src/cli/commands.rs index 10e0314..4a6d8f0 100644 --- a/simplex/crates/cli/src/cli/commands.rs +++ b/crates/cli/src/cli/commands.rs @@ -1,3 +1,4 @@ +use crate::config::ConfigOverride; use clap::{Args, Subcommand}; use std::path::PathBuf; @@ -14,10 +15,7 @@ pub enum Command { #[command(subcommand)] command: TestCommand, }, - Build { - #[arg(env = "OUT_DIR")] - out_dir: Option, - }, + Build, } /// Test management commands @@ -50,3 +48,35 @@ pub struct TestFlags { #[arg(long = "ignored")] pub ignored: bool, } + +/// Build override arguments +#[derive(Debug, Args, Clone)] +pub struct OverrideArgs { + #[command(flatten)] + pub build_args: BuildOverrideArgs, +} + +/// Build override arguments +#[derive(Debug, Args, Clone)] +pub struct BuildOverrideArgs { + /// Output directory for build artifacts + #[arg(global = true, long, env = "OUT_DIR")] + pub out_dir: Option, + /// Flag to generate only files for contracts without module artifacts + #[arg(global = true, long)] + pub only_files: bool, +} + +impl OverrideArgs { + pub fn generate(self) -> Option { + Some(ConfigOverride { + rpc_creds: None, + network: None, + build_conf: if self.build_args.out_dir.is_some() || self.build_args.only_files { + Some(self.build_args) + } else { + None + }, + }) + } +} diff --git a/simplex/crates/cli/src/cli/mod.rs b/crates/cli/src/cli/mod.rs similarity index 67% rename from simplex/crates/cli/src/cli/mod.rs rename to crates/cli/src/cli/mod.rs index c09cd8c..a03527d 100644 --- a/simplex/crates/cli/src/cli/mod.rs +++ b/crates/cli/src/cli/mod.rs @@ -1,12 +1,12 @@ pub mod commands; use crate::cache_storage::CacheStorage; -use crate::cli::commands::{Command, TestCommand, TestFlags}; -use crate::config::{Config, DEFAULT_CONFIG}; +use crate::cli::commands::{Command, OverrideArgs, TestCommand, TestFlags}; +use crate::config::{BuildConf, Config, DEFAULT_CONFIG}; use crate::error::Error; use clap::Parser; -use simplex_macros_core::env::CodeGenerator; use simplex_test::TestClientProvider; +use std::env; use std::path::PathBuf; use std::process::Stdio; use std::sync::Arc; @@ -17,10 +17,13 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[command(about = "CLI for Simplicity Options trading on Liquid")] pub struct Cli { #[arg(short, long, env = "SIMPLEX_CONFIG")] - pub config: Option, + config: Option, #[command(subcommand)] - pub command: commands::Command, + command: Command, + + #[command(flatten)] + override_args: OverrideArgs, } struct TestParams { @@ -39,33 +42,37 @@ impl Cli { /// /// # Errors /// Returns an error if the command execution fails. - pub async fn run(&self) -> Result<(), Error> { - match &self.command { - commands::Command::Init => { + pub async fn run(self) -> Result<(), Error> { + let config_override = self.override_args.generate(); + + match self.command { + Command::Init => { let config_path = Config::get_path()?; std::fs::write(&config_path, DEFAULT_CONFIG)?; println!("Config written to: '{}'", config_path.display()); Ok(()) } - commands::Command::Config => { - let loaded_config = - Config::load_or_discover(self.config.clone()).map_err(|e| Error::ConfigDiscoveryFailure(e))?; - println!("{loaded_config:#?}"); + Command::Config => { + let loaded_config = Config::load_or_discover(self.config.clone()) + .map_err(|e| Error::ConfigDiscoveryFailure(e))? + .override_cfg(config_override)?; + + dbg!(&loaded_config); Ok(()) } - commands::Command::Test { command } => { - let loaded_config = - Config::load_or_discover(self.config.clone()).map_err(|e| Error::ConfigDiscoveryFailure(e))?; - println!("{loaded_config:#?}"); + Command::Test { command } => { + let loaded_config = Config::load_or_discover(self.config.clone()) + .map_err(|e| Error::ConfigDiscoveryFailure(e))? + .override_cfg(config_override)?; - self.run_test_command(loaded_config, command)?; + Self::run_test_command(loaded_config, &command)?; Ok(()) } - commands::Command::Regtest => { - let loaded_config = - Config::load_or_discover(self.config.clone()).map_err(|e| Error::ConfigDiscoveryFailure(e))?; - println!("{loaded_config:#?}"); + Command::Regtest => { + let loaded_config = Config::load_or_discover(self.config.clone()) + .map_err(|e| Error::ConfigDiscoveryFailure(e))? + .override_cfg(config_override)?; let running = Arc::new(AtomicBool::new(true)); let r = running.clone(); @@ -90,31 +97,47 @@ impl Cli { println!("Exiting..."); Ok(()) } - // TODO: add overriding of value or delete - Command::Build { out_dir: _out_dir } => { - let loaded_config = - Config::load_or_discover(self.config.clone()).map_err(|e| Error::ConfigDiscoveryFailure(e))?; + Command::Build => { + let loaded_config = Config::load_or_discover(self.config.clone()) + .map_err(|e| Error::ConfigDiscoveryFailure(e))? + .override_cfg(config_override)?; if loaded_config.build_config.is_none() { return Err(Error::Config( "No build config to build contracts environment, please add appropriate config".to_string(), )); } - - let build_config = loaded_config.build_config.unwrap(); - if build_config.compile_simf.is_empty() { - return Err(Error::Config("No files listed to build contracts environment, please check glob patterns or 'compile_simf' field in config.".to_string())); + dbg!(&loaded_config); + + let build_config = dbg!(BuildConf::check_or_unwrap(loaded_config.build_config)?); + + let out_dir_unwrapped = build_config.out_dir.unwrap(); + let cwd = env::current_dir()?; + + match build_config.only_files { + true => { + simplex_template_gen_core::expand_only_files( + &cwd, + &out_dir_unwrapped, + &build_config.simf_files, + )?; + } + false => { + simplex_template_gen_core::expand_files_with_nested_dirs( + &cwd, + &build_config.base_dir, + &out_dir_unwrapped, + &build_config.simf_files, + )?; + } } - CodeGenerator::generate_files(&build_config.out_dir, &build_config.compile_simf)?; - - println!("{build_config:#?}"); Ok(()) } } } - pub(crate) fn run_test_command(&self, config: Config, command: &TestCommand) -> Result<(), Error> { + pub(crate) fn run_test_command(config: Config, command: &TestCommand) -> Result<(), Error> { let cache_path = CacheStorage::save_cached_test_config(&config.test_config)?; let mut test_command = match command { TestCommand::Integration { additional_flags } => Self::form_test_command(TestParams { @@ -187,7 +210,7 @@ impl Cli { } } test_command.args([command_as_arg]); - dbg!(test_command.get_args()); + let _ = dbg!(test_command.get_args()); test_command .env(simplex_test::TEST_ENV_NAME, params.cache_path) .stdin(Stdio::inherit()) diff --git a/simplex/crates/cli/src/config.rs b/crates/cli/src/config.rs similarity index 61% rename from simplex/crates/cli/src/config.rs rename to crates/cli/src/config.rs index a03a5e8..9bdc00e 100644 --- a/simplex/crates/cli/src/config.rs +++ b/crates/cli/src/config.rs @@ -1,3 +1,6 @@ +use crate::cli::commands::BuildOverrideArgs; +use crate::error::Error; +use globwalk::FileType; use serde::{Deserialize, Serialize}; use simplex_sdk::constants::SimplicityNetwork; use simplex_test::{ElementsDConf, RpcCreds}; @@ -55,18 +58,37 @@ pub struct ProviderConf { pub struct ConfigOverride { pub rpc_creds: Option, pub network: Option, + pub build_conf: Option, } #[derive(Debug, Clone, Deserialize)] pub struct BuildConf { - pub compile_simf: Vec, - pub out_dir: PathBuf, + pub simf_files: Vec, + pub out_dir: Option, + pub base_dir: PathBuf, + pub only_files: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct _BuildConf { - compile_simf: Vec, - out_dir: PathBuf, + simf_files: Vec, + out_dir: Option, + base_dir: Option, + #[serde(default)] + only_files: bool, +} + +impl BuildConf { + pub(crate) fn check_or_unwrap(loaded_config: Option) -> Result { + let loaded_config = loaded_config.unwrap(); + if loaded_config.simf_files.is_empty() { + return Err(Error::Config("No files listed to build contracts environment, please check glob patterns or 'compile_simf' field in config.".to_string())); + } + if loaded_config.out_dir.is_none() { + return Err(Error::Config("No out directory is set to build contracts environment, please check glob patterns or 'out_dir' field in config.".to_string())); + } + Ok(loaded_config) + } } impl Default for ProviderConf { @@ -83,21 +105,33 @@ impl Config { Ok(cwd.join(CONFIG_FILENAME)) } - pub fn discover(cfg_override: Option<&ConfigOverride>) -> Result { - match Config::_discover() { - Ok(mut cfg) => { - if let Some(cfg_override) = cfg_override { - if let Some(test_conf) = cfg_override.rpc_creds.clone() { - cfg.test_config = test_conf; - } - if let Some(network) = cfg_override.network { - cfg.provider_config.simplicity_network = network; + pub fn discover() -> Result { + Config::_discover() + } + + pub fn override_cfg(mut self, cfg_override: Option) -> io::Result { + if let Some(cfg_override) = cfg_override { + if let Some(test_conf) = cfg_override.rpc_creds.clone() { + self.test_config = test_conf; + } + if let Some(network) = cfg_override.network { + self.provider_config.simplicity_network = network; + } + if let Some(build_args) = cfg_override.build_conf { + if let Some(ref mut build_conf) = self.build_config { + if build_args.out_dir.is_some() { + build_conf.out_dir = build_args.out_dir; } + build_conf.only_files |= build_args.only_files; + } else { + // TODO: refactor config gathering, change io error into smth else + return Err(io::Error::other( + "Empty build_conf configuration, configure at least 'base_dir', 'simf_files' and 'out_dir'", + )); } - Ok(cfg) } - Err(e) => Err(e), } + Ok(self) } pub fn load(path_buf: impl AsRef) -> Result { @@ -126,6 +160,22 @@ impl Config { fn from_path(p: impl AsRef) -> Result { std::fs::read_to_string(p.as_ref())?.parse() } + + #[inline] + fn resolve_optional_dir_or_cwd(base: Option) -> io::Result { + match base { + None => std::env::current_dir(), + Some(b) => Ok(resolve_dir_path(b)?), + } + } + + #[inline] + fn resolve_optional_dir(base: Option) -> io::Result> { + match base { + None => Ok(None), + Some(b) => Ok(Some(resolve_dir_path(b)?)), + } + } } impl FromStr for Config { @@ -151,10 +201,15 @@ impl FromStr for Config { }), build_config: match cfg.build { None => None, - Some(x) => Some(BuildConf { - compile_simf: resolve_glob_paths(&x.compile_simf)?, - out_dir: resolve_dir_path(x.out_dir)?, - }), + Some(x) => { + let base_dir = Self::resolve_optional_dir_or_cwd(x.base_dir)?; + Some(BuildConf { + simf_files: resolve_glob_paths(&base_dir, &x.simf_files)?, + out_dir: Self::resolve_optional_dir(x.out_dir)?, + base_dir, + only_files: x.only_files, + }) + } }, }) } @@ -192,23 +247,18 @@ impl Into for _NetworkName { } } -fn resolve_glob_paths(pattern: &[impl AsRef]) -> io::Result> { +fn resolve_glob_paths(base_dir: impl AsRef, patterns: &[impl AsRef]) -> io::Result> { let mut paths = Vec::new(); - for path in pattern.iter().map(|x| resolve_glob_path(x.as_ref())) { - let path = path?; - paths.extend_from_slice(&path); - } - Ok(paths) -} -fn resolve_glob_path(pattern: impl AsRef) -> io::Result> { - let mut paths = Vec::new(); - for path in glob::glob(pattern.as_ref()) - .map_err(|e| io::Error::other(e.to_string()))? - .filter_map(Result::ok) - { - println!("path: '{}', pattern: '{}'", path.display(), pattern.as_ref()); - paths.push(path); + let walker = globwalk::GlobWalkerBuilder::from_patterns(base_dir, patterns) + .follow_links(true) + .file_type(FileType::FILE) + .build()? + .into_iter() + .filter_map(Result::ok); + + for img in walker { + paths.push(img.path().to_path_buf().canonicalize()?); } Ok(paths) } @@ -237,5 +287,6 @@ fn resolve_dir_path(path: impl AsRef) -> io::Result { path_outer.display() ))); } + let path_outer = path_outer.canonicalize()?; Ok(path_outer) } diff --git a/simplex/crates/cli/src/error.rs b/crates/cli/src/error.rs similarity index 94% rename from simplex/crates/cli/src/error.rs rename to crates/cli/src/error.rs index aa41540..8748226 100644 --- a/simplex/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -33,5 +33,5 @@ pub enum Error { /// Errors when generating code for simplicity environment. #[error("Occurred code generation error, error: '{0}'")] - CodeGenerator(#[from] simplex_macros_core::env::CodeGeneratorError), + CodeGenerator(#[from] simplex_template_gen_core::CodeGeneratorError), } diff --git a/simplex/crates/cli/src/lib.rs b/crates/cli/src/lib.rs similarity index 100% rename from simplex/crates/cli/src/lib.rs rename to crates/cli/src/lib.rs diff --git a/simplex/crates/cli/src/logging.rs b/crates/cli/src/logging.rs similarity index 100% rename from simplex/crates/cli/src/logging.rs rename to crates/cli/src/logging.rs diff --git a/crates/macros-core/Cargo.toml b/crates/macros-core/Cargo.toml new file mode 100644 index 0000000..3bb3a65 --- /dev/null +++ b/crates/macros-core/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "simplex-macros-core" +description = "Macro support core for Simplex, the Rust SimplicityHl toolkit. Not intended to be used directly." +version = "0.1.0" +license.workspace = true +edition.workspace = true + +[lints] +workspace = true + +[features] +bincode = [] +serde = ["bincode", "dep:serde"] +default = ["bincode", "serde"] + +[dependencies] +simplicityhl = { workspace = true } +simplex-test = { workspace = true } + +proc-macro2 = { version = "1.0.106", features = ["span-locations"] } +syn = { version = "2.0.114", default-features = false, features = ["proc-macro", "full", "parsing", "derive", "clone-impls", "extra-traits", "printing"] } +quote = { version = "1.0.44" } +serde = { version = "1.0.228", optional = true } diff --git a/simplex/crates/macros-core/src/attr/codegen.rs b/crates/macros-core/src/attr/codegen.rs similarity index 90% rename from simplex/crates/macros-core/src/attr/codegen.rs rename to crates/macros-core/src/attr/codegen.rs index c089898..041a61a 100644 --- a/simplex/crates/macros-core/src/attr/codegen.rs +++ b/crates/macros-core/src/attr/codegen.rs @@ -1,6 +1,5 @@ use crate::attr::SimfContent; use crate::attr::types::RustType; -use proc_macro2::Ident; use quote::{format_ident, quote}; use simplicityhl::str::WitnessName; use simplicityhl::{AbiMeta, Parameters, ResolvedType, WitnessTypes}; @@ -144,26 +143,24 @@ impl WitnessStruct { } } - impl simplex::serde::Serialize for #struct_name { + impl ::simplex::serde::Serialize for #struct_name { fn serialize(&self, serializer: S) -> Result where - S: simplex::serde::Serializer, + S: ::simplex::serde::Serializer, { self.build_arguments().serialize(serializer) } } - impl<'de> simplex::serde::Deserialize<'de> for #struct_name { + impl<'de> ::simplex::serde::Deserialize<'de> for #struct_name { fn deserialize(deserializer: D) -> Result where - D: simplex::serde::Deserializer<'de>, + D: ::simplex::serde::Deserializer<'de>, { let x = ::simplicityhl::Arguments::deserialize(deserializer)?; Self::from_arguments(&x).map_err(simplex::serde::de::Error::custom) } } - - // impl ::simplex_core::Encodable for #struct_name {} }, }) } @@ -183,13 +180,13 @@ impl WitnessStruct { Ok(GeneratedWitnessTokens { imports: quote! { - use std::collections::HashMap; - use simplicityhl::{WitnessValues, Value, ResolvedType}; - use simplicityhl::value::{UIntValue, ValueInner}; - use simplicityhl::num::U256; - use simplicityhl::str::WitnessName; - use simplicityhl::types::TypeConstructible; - use simplicityhl::value::ValueConstructible; + use ::std::collections::HashMap; + use ::simplicityhl::{WitnessValues, Value, ResolvedType}; + use ::simplicityhl::value::{UIntValue, ValueInner}; + use ::simplicityhl::num::U256; + use ::simplicityhl::str::WitnessName; + use ::simplicityhl::types::TypeConstructible; + use ::simplicityhl::value::ValueConstructible; use ::simplex::simplex_sdk::program::WitnessTrait; }, struct_token_stream: quote! { @@ -219,26 +216,24 @@ impl WitnessStruct { } } - impl simplex::serde::Serialize for #struct_name { + impl ::simplex::serde::Serialize for #struct_name { fn serialize(&self, serializer: S) -> Result where - S: simplex::serde::Serializer, + S: ::simplex::serde::Serializer, { self.build_witness().serialize(serializer) } } - impl<'de> simplex::serde::Deserialize<'de> for #struct_name { + impl<'de> ::simplex::serde::Deserialize<'de> for #struct_name { fn deserialize(deserializer: D) -> Result where - D: simplex::serde::Deserializer<'de>, + D: ::simplex::serde::Deserializer<'de>, { let x = ::simplicityhl::WitnessValues::deserialize(deserializer)?; Self::from_witness(&x).map_err(simplex::serde::de::Error::custom) } } - - // impl ::simplex_core::Encodable for #struct_name {} }, }) } @@ -258,6 +253,7 @@ impl WitnessStruct { witness_values: WitnessStruct::generate_witness_fields(meta.iter())?, }) } + fn generate_witness_fields<'a>( iter: impl Iterator, ) -> syn::Result> { @@ -331,7 +327,7 @@ impl WitnessStruct { } } -pub(crate) fn convert_contract_name_to_struct_name(contract_name: &str) -> String { +pub fn convert_contract_name_to_struct_name(contract_name: &str) -> String { let words: Vec = contract_name .split('_') .filter(|w| !w.is_empty()) @@ -346,10 +342,10 @@ pub(crate) fn convert_contract_name_to_struct_name(contract_name: &str) -> Strin words.join("") } -pub(crate) fn convert_contract_name_to_contract_source_const(contract_name: &str) -> Ident { +pub fn convert_contract_name_to_contract_source_const(contract_name: &str) -> proc_macro2::Ident { format_ident!("{}_CONTRACT_SOURCE", contract_name.to_uppercase()) } -pub(crate) fn convert_contract_name_to_contract_module(contract_name: &str) -> Ident { +pub fn convert_contract_name_to_contract_module(contract_name: &str) -> proc_macro2::Ident { format_ident!("derived_{}", contract_name) } diff --git a/simplex/crates/macros-core/src/attr/mod.rs b/crates/macros-core/src/attr/mod.rs similarity index 57% rename from simplex/crates/macros-core/src/attr/mod.rs rename to crates/macros-core/src/attr/mod.rs index 0d367ba..d3b1c03 100644 --- a/simplex/crates/macros-core/src/attr/mod.rs +++ b/crates/macros-core/src/attr/mod.rs @@ -12,12 +12,6 @@ use proc_macro2::Span; use quote::quote; use simplicityhl::AbiMeta; use std::error::Error; -// TODO(Illia): add bincode generation feature (i.e. require bincode dependencies) -// TODO(Illia): add conditional compilation for simplicity-core to e included automatically - -// TODO(Illia): automatically derive bincode implementation -// TODO(Illia): extract either:serde feature and use it when simplicityhl has serde feature -// TODO(Illia): add features /// Expands helper functions for the given Simf content and metadata. /// @@ -52,46 +46,7 @@ fn construct_program_helpers(derived_meta: &SimfContractMeta) -> proc_macro2::To let contract_source_name = &derived_meta.contract_source_const_name; quote! { - // use simplicityhl::elements::Address; - // use simplicityhl::simplicity::bitcoin::XOnlyPublicKey; - // use simplex::simplex_core::{create_p2tr_address, load_program, ProgramError, SimplicityNetwork}; - // use simplicityhl::CompiledProgram; - pub const #contract_source_name: &str = #contract_content; - - /// Get the options template program for instantiation. - /// - /// # Panics - /// - if the embedded source fails to compile (should never happen). - // #[must_use] - // pub fn get_template_program() -> ::simplicityhl::TemplateProgram { - // ::simplicityhl::TemplateProgram::new(#contract_source_name).expect(#error_msg) - // } - - /// Compile option offer program with the given arguments. - /// - /// # Errors - /// - /// Returns error if compilation fails. - // pub fn get_loaded_program( - // arguments: &#contract_arguments_struct_name, - // ) -> Result { - // load_program(#contract_source_name, arguments.build_arguments()) - // } - - /// Get compiled option offer program, panicking on failure. - /// - /// # Panics - /// - /// Panics if program instantiation fails. - // #[must_use] - // pub fn get_compiled_program(arguments: &#contract_arguments_struct_name) -> CompiledProgram { - // let program = get_template_program(); - - // program - // .instantiate(arguments.build_arguments(), true) - // .unwrap() - // } } } diff --git a/simplex/crates/macros-core/src/attr/parse.rs b/crates/macros-core/src/attr/parse.rs similarity index 96% rename from simplex/crates/macros-core/src/attr/parse.rs rename to crates/macros-core/src/attr/parse.rs index b146466..28bb9dc 100644 --- a/simplex/crates/macros-core/src/attr/parse.rs +++ b/crates/macros-core/src/attr/parse.rs @@ -7,10 +7,6 @@ use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{Expr, ExprLit, Lit}; -// TODO: come up with an idea of how to parse constant values and evaluate constant values that are passed inside -// pub const OPTION_SOURCE: &str = include_str!("source_simf/options.simf"); -// include_simf_source!(OPTION_SOURCE); - pub struct SynFilePath { _span_file: String, path_literal: String, diff --git a/simplex/crates/macros-core/src/attr/program.rs b/crates/macros-core/src/attr/program.rs similarity index 100% rename from simplex/crates/macros-core/src/attr/program.rs rename to crates/macros-core/src/attr/program.rs diff --git a/simplex/crates/macros-core/src/attr/types.rs b/crates/macros-core/src/attr/types.rs similarity index 100% rename from simplex/crates/macros-core/src/attr/types.rs rename to crates/macros-core/src/attr/types.rs diff --git a/simplex/crates/macros-core/src/lib.rs b/crates/macros-core/src/lib.rs similarity index 72% rename from simplex/crates/macros-core/src/lib.rs rename to crates/macros-core/src/lib.rs index 4194710..414eb5d 100644 --- a/simplex/crates/macros-core/src/lib.rs +++ b/crates/macros-core/src/lib.rs @@ -1,8 +1,6 @@ #![warn(clippy::all, clippy::pedantic)] pub mod attr; -/// Module releted to simplex environment generation -pub mod env; pub mod test; /// Expands the `include_simf` macro. @@ -24,10 +22,3 @@ pub fn expand_include_simf(input: &attr::parse::SynFilePath) -> syn::Result syn::Result { test::expand(args, input) } - -pub fn expand_simplex_contract_enviroment( - outdir: impl AsRef, - simfs: &[impl AsRef], -) -> Result<(), env::CodeGeneratorError> { - env::CodeGenerator::generate_files(outdir, simfs) -} diff --git a/simplex/crates/macros-core/src/test/mod.rs b/crates/macros-core/src/test/mod.rs similarity index 91% rename from simplex/crates/macros-core/src/test/mod.rs rename to crates/macros-core/src/test/mod.rs index d255f48..0309d45 100644 --- a/simplex/crates/macros-core/src/test/mod.rs +++ b/crates/macros-core/src/test/mod.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; +use quote::quote; use syn::parse::Parser; pub(crate) fn expand_inner(input: &syn::ItemFn, args: AttributeArgs) -> syn::Result { @@ -10,10 +10,8 @@ pub(crate) fn expand_inner(input: &syn::ItemFn, args: AttributeArgs) -> syn::Res let attrs = &input.attrs; let fn_name_str = name.to_string(); - let args_str = args.clone().to_token_stream().to_string(); let parsed_attribute_args = parse_args(args)?; let simplex_test_env = simplex_test::TEST_ENV_NAME; - let ident = format!("{input:#?}"); let ok_path_generation = match parsed_attribute_args.config_option { ConfigOpt::Config => { quote! { @@ -57,8 +55,6 @@ pub(crate) fn expand_inner(input: &syn::ItemFn, args: AttributeArgs) -> syn::Res } #ok_path_generation }; - // println!("fn name: {}, \n ident: {}", #fn_name_str, #ident); - // println!("input: {}, \n AttributeArgs: {}", "", #args_str); #name(test_context) } diff --git a/simplex/crates/macros/Cargo.toml b/crates/macros/Cargo.toml similarity index 100% rename from simplex/crates/macros/Cargo.toml rename to crates/macros/Cargo.toml diff --git a/simplex/crates/macros/src/lib.rs b/crates/macros/src/lib.rs similarity index 100% rename from simplex/crates/macros/src/lib.rs rename to crates/macros/src/lib.rs diff --git a/simplex/crates/provider/Cargo.toml b/crates/provider/Cargo.toml similarity index 100% rename from simplex/crates/provider/Cargo.toml rename to crates/provider/Cargo.toml diff --git a/simplex/crates/provider/api-esplora.md b/crates/provider/api-esplora.md similarity index 100% rename from simplex/crates/provider/api-esplora.md rename to crates/provider/api-esplora.md diff --git a/simplex/crates/provider/api-waterfall.md b/crates/provider/api-waterfall.md similarity index 100% rename from simplex/crates/provider/api-waterfall.md rename to crates/provider/api-waterfall.md diff --git a/simplex/crates/provider/src/elements_rpc/mod.rs b/crates/provider/src/elements_rpc/mod.rs similarity index 100% rename from simplex/crates/provider/src/elements_rpc/mod.rs rename to crates/provider/src/elements_rpc/mod.rs diff --git a/simplex/crates/provider/src/elements_rpc/types.rs b/crates/provider/src/elements_rpc/types.rs similarity index 100% rename from simplex/crates/provider/src/elements_rpc/types.rs rename to crates/provider/src/elements_rpc/types.rs index bcb358a..9de3e84 100644 --- a/simplex/crates/provider/src/elements_rpc/types.rs +++ b/crates/provider/src/elements_rpc/types.rs @@ -105,8 +105,8 @@ pub struct BalanceDetails { #[derive(Default, Debug, Clone, Copy)] pub enum AddressType { Legacy, - #[default] P2shSegwit, + #[default] Bech32, Bech32m, } diff --git a/simplex/crates/provider/src/error.rs b/crates/provider/src/error.rs similarity index 100% rename from simplex/crates/provider/src/error.rs rename to crates/provider/src/error.rs diff --git a/simplex/crates/provider/src/esplora/mod.rs b/crates/provider/src/esplora/mod.rs similarity index 99% rename from simplex/crates/provider/src/esplora/mod.rs rename to crates/provider/src/esplora/mod.rs index e3e81d9..bda1ba8 100644 --- a/simplex/crates/provider/src/esplora/mod.rs +++ b/crates/provider/src/esplora/mod.rs @@ -1,7 +1,5 @@ mod types; -// TODO(Illia): remove #[allow(dead_code)] - use crate::error::ExplorerError; use crate::esplora::deserializable::TypeConversion; use simplicityhl::elements::pset::serialize::Deserialize; @@ -31,7 +29,6 @@ pub struct EsploraConfig { } // TODO: Illia add caching as optional parameter -// TODO: Add api backend trait implementation impl EsploraClientBuilder { fn default_url() -> String { ESPLORA_LIQUID_TESTNET.to_string() @@ -51,8 +48,11 @@ impl EsploraClientBuilder { } } - pub fn custom(url: impl Into) -> Self { - // todo: remove trailling slash + pub fn custom(url: impl AsRef) -> Self { + let url = { + let url = url.as_ref(); + url.trim_matches('/').to_string() + }; EsploraClientBuilder { url: Some(url.into()) } } diff --git a/simplex/crates/provider/src/esplora/types.rs b/crates/provider/src/esplora/types.rs similarity index 100% rename from simplex/crates/provider/src/esplora/types.rs rename to crates/provider/src/esplora/types.rs diff --git a/simplex/crates/provider/src/lib.rs b/crates/provider/src/lib.rs similarity index 100% rename from simplex/crates/provider/src/lib.rs rename to crates/provider/src/lib.rs diff --git a/simplex/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml similarity index 100% rename from simplex/crates/sdk/Cargo.toml rename to crates/sdk/Cargo.toml diff --git a/simplex/crates/sdk/src/constants.rs b/crates/sdk/src/constants.rs similarity index 100% rename from simplex/crates/sdk/src/constants.rs rename to crates/sdk/src/constants.rs diff --git a/simplex/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs similarity index 100% rename from simplex/crates/sdk/src/lib.rs rename to crates/sdk/src/lib.rs index 96ab949..5dc4e44 100644 --- a/simplex/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -1,7 +1,7 @@ pub mod constants; pub mod presets; pub mod program; -pub mod transaction; pub mod provider; pub mod signer; +pub mod transaction; pub mod utils; diff --git a/simplex/crates/sdk/src/presets/mod.rs b/crates/sdk/src/presets/mod.rs similarity index 100% rename from simplex/crates/sdk/src/presets/mod.rs rename to crates/sdk/src/presets/mod.rs diff --git a/simplex/crates/sdk/src/presets/p2pk.rs b/crates/sdk/src/presets/p2pk.rs similarity index 100% rename from simplex/crates/sdk/src/presets/p2pk.rs rename to crates/sdk/src/presets/p2pk.rs diff --git a/example/simf/p2pk.simf b/crates/sdk/src/presets/simf/p2pk.simf similarity index 100% rename from example/simf/p2pk.simf rename to crates/sdk/src/presets/simf/p2pk.simf diff --git a/simplex/crates/sdk/src/program/arguments.rs b/crates/sdk/src/program/arguments.rs similarity index 100% rename from simplex/crates/sdk/src/program/arguments.rs rename to crates/sdk/src/program/arguments.rs index a855e46..60799cd 100644 --- a/simplex/crates/sdk/src/program/arguments.rs +++ b/crates/sdk/src/program/arguments.rs @@ -1,5 +1,5 @@ -use simplicityhl::Arguments; use dyn_clone::DynClone; +use simplicityhl::Arguments; pub trait ArgumentsTrait: DynClone { fn build_arguments(&self) -> Arguments; diff --git a/simplex/crates/sdk/src/program/error.rs b/crates/sdk/src/program/error.rs similarity index 100% rename from simplex/crates/sdk/src/program/error.rs rename to crates/sdk/src/program/error.rs diff --git a/simplex/crates/sdk/src/program/mod.rs b/crates/sdk/src/program/mod.rs similarity index 100% rename from simplex/crates/sdk/src/program/mod.rs rename to crates/sdk/src/program/mod.rs diff --git a/simplex/crates/sdk/src/program/program.rs b/crates/sdk/src/program/program.rs similarity index 100% rename from simplex/crates/sdk/src/program/program.rs rename to crates/sdk/src/program/program.rs diff --git a/simplex/crates/sdk/src/program/witness.rs b/crates/sdk/src/program/witness.rs similarity index 100% rename from simplex/crates/sdk/src/program/witness.rs rename to crates/sdk/src/program/witness.rs index b6d0735..374f8f6 100644 --- a/simplex/crates/sdk/src/program/witness.rs +++ b/crates/sdk/src/program/witness.rs @@ -1,5 +1,5 @@ -use simplicityhl::WitnessValues; use dyn_clone::DynClone; +use simplicityhl::WitnessValues; pub trait WitnessTrait: DynClone { fn build_witness(&self) -> WitnessValues; diff --git a/simplex/crates/sdk/src/provider/error.rs b/crates/sdk/src/provider/error.rs similarity index 100% rename from simplex/crates/sdk/src/provider/error.rs rename to crates/sdk/src/provider/error.rs diff --git a/simplex/crates/sdk/src/provider/esplora.rs b/crates/sdk/src/provider/esplora.rs similarity index 100% rename from simplex/crates/sdk/src/provider/esplora.rs rename to crates/sdk/src/provider/esplora.rs diff --git a/simplex/crates/sdk/src/provider/mod.rs b/crates/sdk/src/provider/mod.rs similarity index 100% rename from simplex/crates/sdk/src/provider/mod.rs rename to crates/sdk/src/provider/mod.rs diff --git a/simplex/crates/sdk/src/provider/provider.rs b/crates/sdk/src/provider/provider.rs similarity index 100% rename from simplex/crates/sdk/src/provider/provider.rs rename to crates/sdk/src/provider/provider.rs diff --git a/simplex/crates/sdk/src/signer/error.rs b/crates/sdk/src/signer/error.rs similarity index 100% rename from simplex/crates/sdk/src/signer/error.rs rename to crates/sdk/src/signer/error.rs diff --git a/simplex/crates/sdk/src/signer/mod.rs b/crates/sdk/src/signer/mod.rs similarity index 100% rename from simplex/crates/sdk/src/signer/mod.rs rename to crates/sdk/src/signer/mod.rs diff --git a/simplex/crates/sdk/src/signer/signer.rs b/crates/sdk/src/signer/signer.rs similarity index 100% rename from simplex/crates/sdk/src/signer/signer.rs rename to crates/sdk/src/signer/signer.rs diff --git a/simplex/crates/sdk/src/transaction/error.rs b/crates/sdk/src/transaction/error.rs similarity index 100% rename from simplex/crates/sdk/src/transaction/error.rs rename to crates/sdk/src/transaction/error.rs diff --git a/simplex/crates/sdk/src/transaction/final_transaction.rs b/crates/sdk/src/transaction/final_transaction.rs similarity index 100% rename from simplex/crates/sdk/src/transaction/final_transaction.rs rename to crates/sdk/src/transaction/final_transaction.rs diff --git a/simplex/crates/sdk/src/transaction/mod.rs b/crates/sdk/src/transaction/mod.rs similarity index 100% rename from simplex/crates/sdk/src/transaction/mod.rs rename to crates/sdk/src/transaction/mod.rs diff --git a/simplex/crates/sdk/src/transaction/partial_input.rs b/crates/sdk/src/transaction/partial_input.rs similarity index 100% rename from simplex/crates/sdk/src/transaction/partial_input.rs rename to crates/sdk/src/transaction/partial_input.rs diff --git a/simplex/crates/sdk/src/transaction/partial_output.rs b/crates/sdk/src/transaction/partial_output.rs similarity index 100% rename from simplex/crates/sdk/src/transaction/partial_output.rs rename to crates/sdk/src/transaction/partial_output.rs diff --git a/simplex/crates/sdk/src/utils.rs b/crates/sdk/src/utils.rs similarity index 100% rename from simplex/crates/sdk/src/utils.rs rename to crates/sdk/src/utils.rs diff --git a/simplex/crates/simplex/Cargo.toml b/crates/simplex/Cargo.toml similarity index 100% rename from simplex/crates/simplex/Cargo.toml rename to crates/simplex/Cargo.toml diff --git a/simplex/crates/simplex/src/lib.rs b/crates/simplex/src/lib.rs similarity index 100% rename from simplex/crates/simplex/src/lib.rs rename to crates/simplex/src/lib.rs diff --git a/crates/simplex/tests/compiletest.rs b/crates/simplex/tests/compiletest.rs new file mode 100644 index 0000000..0a2eabb --- /dev/null +++ b/crates/simplex/tests/compiletest.rs @@ -0,0 +1,11 @@ +const SLOW_TEST_ENV: &str = "RUN_SLOW_TESTS"; +#[test] +fn ui() { + if let Err(_) = std::env::var(SLOW_TEST_ENV) { + tracing::trace!("Set '{SLOW_TEST_ENV}' to true in order to run a test"); + return; + } + + let t = trybuild::TestCases::new(); + t.pass("tests/ui/*.rs"); +} diff --git a/simplex/crates/simplex/tests/simplex_test.rs b/crates/simplex/tests/simplex_test.rs similarity index 97% rename from simplex/crates/simplex/tests/simplex_test.rs rename to crates/simplex/tests/simplex_test.rs index 8b9b2cd..fb2608e 100644 --- a/simplex/crates/simplex/tests/simplex_test.rs +++ b/crates/simplex/tests/simplex_test.rs @@ -80,9 +80,7 @@ fn test_invocation_tx_tracking() -> anyhow::Result<()> { // - // p2tr - // TODO: uncomment and fix dbg!(ElementsRpcClient::validateaddress(rpc.as_ref(), &p2pk.to_string())?); - // ElementsRpcClient::importaddress(rpc.as_ref(), &p2pk.to_string(), None, None, None)?; // broadcast, fetch fee transaction @@ -126,6 +124,10 @@ fn test_invocation_tx_tracking() -> anyhow::Result<()> { ElementsRpcClient::sweep_initialfreecoins(rpc.as_ref()).unwrap(); ElementsRpcClient::generate_blocks(rpc.as_ref(), 100).unwrap(); } + // TODO: remove berkeley + // addresstype - bech32 + + // let user1_addr = ElementsRpcClient::getnewaddress(rpc.as_ref(), "", AddressType::default()).unwrap(); let user2_addr = ElementsRpcClient::getnewaddress(rpc.as_ref(), "", AddressType::default()).unwrap(); diff --git a/simplex/crates/simplex/tests/ui/array_tr_storage.rs b/crates/simplex/tests/ui/array_tr_storage.rs similarity index 91% rename from simplex/crates/simplex/tests/ui/array_tr_storage.rs rename to crates/simplex/tests/ui/array_tr_storage.rs index 006ed37..6a9da79 100644 --- a/simplex/crates/simplex/tests/ui/array_tr_storage.rs +++ b/crates/simplex/tests/ui/array_tr_storage.rs @@ -1,7 +1,7 @@ use simplex::simplex_macros::*; use simplex::simplex_sdk::program::{WitnessTrait, ArgumentsTrait}; -include_simf!("../../../../crates/simplex/tests/ui/array_tr_storage.simf"); +include_simf!("../../../../crates/simplex/tests/ui_simfs/array_tr_storage.simf"); fn main() -> Result<(), String> { let original_witness = derived_array_tr_storage::ArrayTrStorageWitness { diff --git a/simplex/crates/simplex/tests/ui/bytes32_tr_storage.rs b/crates/simplex/tests/ui/bytes32_tr_storage.rs similarity index 90% rename from simplex/crates/simplex/tests/ui/bytes32_tr_storage.rs rename to crates/simplex/tests/ui/bytes32_tr_storage.rs index 241431d..4ffc886 100644 --- a/simplex/crates/simplex/tests/ui/bytes32_tr_storage.rs +++ b/crates/simplex/tests/ui/bytes32_tr_storage.rs @@ -1,7 +1,7 @@ use simplex::simplex_macros::*; use simplex::simplex_sdk::program::{WitnessTrait, ArgumentsTrait}; -include_simf!("../../../../crates/simplex/tests/ui/bytes32_tr_storage.simf"); +include_simf!("../../../../crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf"); fn main() -> Result<(), String> { let original_witness = derived_bytes32_tr_storage::Bytes32TrStorageWitness { diff --git a/simplex/crates/simplex/tests/ui/dual_currency_deposit.rs b/crates/simplex/tests/ui/dual_currency_deposit.rs similarity index 95% rename from simplex/crates/simplex/tests/ui/dual_currency_deposit.rs rename to crates/simplex/tests/ui/dual_currency_deposit.rs index 9919676..8c48fca 100644 --- a/simplex/crates/simplex/tests/ui/dual_currency_deposit.rs +++ b/crates/simplex/tests/ui/dual_currency_deposit.rs @@ -1,7 +1,7 @@ use simplex::simplex_macros::*; use simplex::simplex_sdk::program::{WitnessTrait, ArgumentsTrait}; -include_simf!("../../../../crates/simplex/tests/ui/dual_currency_deposit.simf"); +include_simf!("../../../../crates/simplex/tests/ui_simfs/dual_currency_deposit.simf"); fn main() -> Result<(), String> { let original_witness = derived_dual_currency_deposit::DualCurrencyDepositWitness { diff --git a/simplex/crates/simplex/tests/ui/option_offer.rs b/crates/simplex/tests/ui/option_offer.rs similarity index 92% rename from simplex/crates/simplex/tests/ui/option_offer.rs rename to crates/simplex/tests/ui/option_offer.rs index 1cfc681..8393522 100644 --- a/simplex/crates/simplex/tests/ui/option_offer.rs +++ b/crates/simplex/tests/ui/option_offer.rs @@ -1,7 +1,7 @@ use simplex::simplex_macros::*; use simplex::simplex_sdk::program::{WitnessTrait, ArgumentsTrait}; -include_simf!("../../../../crates/simplex/tests/ui/option_offer.simf"); +include_simf!("../../../../crates/simplex/tests/ui_simfs/option_offer.simf"); fn main() -> Result<(), String> { let original_witness = derived_option_offer::OptionOfferWitness { path: simplex::either::Left((0, false)) }; diff --git a/simplex/crates/simplex/tests/ui/options.rs b/crates/simplex/tests/ui/options.rs similarity index 94% rename from simplex/crates/simplex/tests/ui/options.rs rename to crates/simplex/tests/ui/options.rs index 80d8aef..58b5a0e 100644 --- a/simplex/crates/simplex/tests/ui/options.rs +++ b/crates/simplex/tests/ui/options.rs @@ -1,7 +1,7 @@ use simplex::simplex_macros::*; use simplex::simplex_sdk::program::{WitnessTrait, ArgumentsTrait}; -include_simf!("../../../../crates/simplex/tests/ui/options.simf"); +include_simf!("../../../../crates/simplex/tests/ui_simfs/options.simf"); fn main() -> Result<(), String> { let original_witness = derived_options::OptionsWitness { diff --git a/simplex/crates/simplex/tests/ui/simple_storage.rs b/crates/simplex/tests/ui/simple_storage.rs similarity index 84% rename from simplex/crates/simplex/tests/ui/simple_storage.rs rename to crates/simplex/tests/ui/simple_storage.rs index ea1a329..d2a6ba5 100644 --- a/simplex/crates/simplex/tests/ui/simple_storage.rs +++ b/crates/simplex/tests/ui/simple_storage.rs @@ -1,7 +1,7 @@ use simplex::simplex_macros::*; -use simplex::simplex_sdk::witness::{WitnessTrait, ArgumentsTrait}; +use simplex::simplex_sdk::program::{WitnessTrait, ArgumentsTrait}; -include_simf!("../../../../crates/simplex/tests/ui/simple_storage.simf"); +include_simf!("../../../../crates/simplex/tests/ui_simfs/simple_storage.simf"); fn main() -> Result<(), String> { let original_witness = derived_simple_storage::SimpleStorageWitness { diff --git a/crates/simplex/tests/ui_simfs/array_tr_storage.simf b/crates/simplex/tests/ui_simfs/array_tr_storage.simf new file mode 100644 index 0000000..4918cf3 --- /dev/null +++ b/crates/simplex/tests/ui_simfs/array_tr_storage.simf @@ -0,0 +1,81 @@ +/* + * Extends `bytes32_tr_storage` using `array_fold` for larger buffers. + * Optimized for small, fixed-size states where linear hashing is more efficient + * than Merkle Trees. By avoiding proof overhead like sibling hashes, we reduce + * witness size and simplify contract logic for small N. + * This approach is particularly advantageous when updating all slots within every transaction. + */ + +fn hash_array_tr_storage(elem: u256, ctx: Ctx8) -> Ctx8 { + jet::sha_256_ctx_8_add_32(ctx, elem) +} + +fn hash_array_tr_storage_with_update(elem: u256, triplet: (Ctx8, u16, u16)) -> (Ctx8, u16, u16) { + let (ctx, i, changed_index): (Ctx8, u16, u16) = triplet; + + match jet::eq_16(i, changed_index) { + true => { + let (_, val): (bool, u16) = jet::increment_16(i); + + // There may be arbitrary logic here + let (state1, state2, state3, state4): (u64, u64, u64, u64) = ::into(elem); + let new_state4: u64 = 20; + + let new_state: u256 = <(u64, u64, u64, u64)>::into((state1, state2, state3, new_state4)); + ( + jet::sha_256_ctx_8_add_32(ctx, new_state), + val, + changed_index, + ) + }, + false => { + let (_, val): (bool, u16) = jet::increment_16(i); + ( + jet::sha_256_ctx_8_add_32(ctx, elem), + val, + changed_index, + ) + } + } +} + +fn script_hash_for_input_script(state: [u256; 3], changed_index: Option) -> u256 { + let tap_leaf: u256 = jet::tapleaf_hash(); + let ctx: Ctx8 = jet::tapdata_init(); + + let (ctx, _, _): (Ctx8, u16, u16) = match changed_index { + Some(ind: u16) => { + array_fold::(state, (ctx, 0, ind)) + }, + None => { + (array_fold::(state, ctx), 0, 0) + } + }; + + let computed: u256 = jet::sha_256_ctx_8_finalize(ctx); + let tap_node: u256 = jet::build_tapbranch(tap_leaf, computed); + + let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; + let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node); + + let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init(); + let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32 + let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key); + jet::sha_256_ctx_8_finalize(hash_ctx3) +} + +fn main() { + let state: [u256; 3] = witness::STATE; + + // Assert that the input is correct, i.e. "load". + assert!(jet::eq_256( + script_hash_for_input_script(state, None), + unwrap(jet::input_script_hash(jet::current_index())) + )); + + // Assert that the output is correct, i.e. "store". + assert!(jet::eq_256( + script_hash_for_input_script(state, Some(witness::CHANGED_INDEX)), + unwrap(jet::output_script_hash(jet::current_index())) + )); +} \ No newline at end of file diff --git a/crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf b/crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf new file mode 100644 index 0000000..0d11b5f --- /dev/null +++ b/crates/simplex/tests/ui_simfs/bytes32_tr_storage.simf @@ -0,0 +1,66 @@ +/* + * Computes the "State Commitment" — the expected Script PubKey (address) + * for a specific state value. + * + * HOW IT WORKS: + * In Simplicity/Liquid, state is not stored in a dedicated database. Instead, + * it is verified via a "Commitment Scheme" inside the Taproot tree of the UTXO. + * + * This function reconstructs the Taproot structure to validate that the provided + * witness data (state_data) was indeed cryptographically embedded into the + * transaction output that is currently being spent. + * + * LOGIC FLOW: + * 1. Takes state_data (passed via witness at runtime). + * 2. Hashes it as a non-executable TapData leaf. + * 3. Combines it with the current program's CMR (tapleaf_hash). + * 4. Derives the tweaked_key (Internal Key + Merkle Root). + * 5. Returns the final SHA256 script hash (SegWit v1). + * + * USAGE: + * - In main, we verify: CalculatedHash(witness::STATE) == input_script_hash. + * - This assertion proves that the UTXO is "locked" not just by the code, + * but specifically by THIS instance of the state data. + */ + +fn script_hash_for_input_script(state_data: u256) -> u256 { + // This is the bulk of our "compute state commitment" logic from above. + let tap_leaf: u256 = jet::tapleaf_hash(); + let state_ctx1: Ctx8 = jet::tapdata_init(); + let state_ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(state_ctx1, state_data); + let state_leaf: u256 = jet::sha_256_ctx_8_finalize(state_ctx2); + let tap_node: u256 = jet::build_tapbranch(tap_leaf, state_leaf); + + // Compute a taptweak using this. + let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; + let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node); + + // Turn the taptweak into a script hash + let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init(); + let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32 + let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key); + jet::sha_256_ctx_8_finalize(hash_ctx3) +} + +fn main() { + let state_data: u256 = witness::STATE; + let (state1, state2, state3, state4): (u64, u64, u64, u64) = ::into(state_data); + + // Assert that the input is correct, i.e. "load". + assert!(jet::eq_256( + script_hash_for_input_script(state_data), + unwrap(jet::input_script_hash(jet::current_index())) + )); + + // Do a state update (and fail on 64-bit overflow even though we've got 192 other + // bits we could be using..) + let (carry, new_state4): (bool, u64) = jet::increment_64(state4); + assert!(jet::eq_1(::into(carry), 0)); + + let new_state: u256 = <(u64, u64, u64, u64)>::into((state1, state2, state3, new_state4)); + // Assert that the output is correct, i.e. "store". + assert!(jet::eq_256( + script_hash_for_input_script(new_state), + unwrap(jet::output_script_hash(jet::current_index())) + )); +} \ No newline at end of file diff --git a/crates/simplex/tests/ui_simfs/dual_currency_deposit.simf b/crates/simplex/tests/ui_simfs/dual_currency_deposit.simf new file mode 100644 index 0000000..e1a460a --- /dev/null +++ b/crates/simplex/tests/ui_simfs/dual_currency_deposit.simf @@ -0,0 +1,592 @@ +/* + * DCD: Dual Currency Deposit – price-attested settlement and funding windows + * + * Flows implemented: + * - Maker funding: deposit settlement asset and collateral, issue grantor tokens + * - Taker funding: deposit collateral in window and receive filler tokens + * - Settlement: at SETTLEMENT_HEIGHT, oracle Schnorr signature over (height, price) + * selects LBTC vs ALT branch based on price <= STRIKE_PRICE + * - Early/post-expiry termination: taker returns filler; maker burns grantor tokens + * - Merge: consolidate 2/3/4 token UTXOs + * + * All amounts and asset/script invariants are enforced on-chain; time guards use + * fallback locktime and height checks. + * + * Batching discussion: https://github.com/BlockstreamResearch/simplicity-contracts/issues/4 + */ + +// Verify Schnorr signature against SHA256 of (u32 || u64) +fn checksig_priceblock(pk: Pubkey, current_block_height: u32, price_at_current_block_height: u64, sig: Signature) { + let hasher: Ctx8 = jet::sha_256_ctx_8_init(); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, current_block_height); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_8(hasher, price_at_current_block_height); + let msg: u256 = jet::sha_256_ctx_8_finalize(hasher); + jet::bip_0340_verify((pk, msg), sig); +} + +// Signed <= using XOR with 0x8000.. bias: a<=b (signed) iff (a^bias) <= (b^bias) (unsigned) +fn signed_le_u64(a_bits: u64, b_bits: u64) -> bool { + let bias: u64 = 0x8000000000000000; + jet::le_64(jet::xor_64(a_bits, bias), jet::xor_64(b_bits, bias)) +} + +fn signed_lt_u64(a: u64, b: u64) -> bool { + let bias: u64 = 0x8000000000000000; + jet::lt_64(jet::xor_64(a, bias), jet::xor_64(b, bias)) +} + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +/// Assert: base_amount * basis_point_percentage == provided_amount * MAX_BASIS_POINTS +fn constraint_percentage(base_amount: u64, basis_point_percentage: u64, provided_amount: u64) { + let MAX_BASIS_POINTS: u64 = 10000; + + let arg1: u256 = <(u128, u128)>::into((0, jet::multiply_64(base_amount, basis_point_percentage))); + let arg2: u256 = <(u128, u128)>::into((0, jet::multiply_64(provided_amount, MAX_BASIS_POINTS))); + + assert!(jet::eq_256(arg1, arg2)); +} + +fn get_output_script_hash(index: u32) -> u256 { + unwrap(jet::output_script_hash(index)) +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +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_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 ensure_one_bit_or(bit1: bool, bit2: bool) { + assert!( + jet::eq_1( + ::into(jet::or_1(::into(bit1), ::into(bit2))), + 1 + ) + ); +} + +fn increment_by(index: u32, amount: u32) -> u32 { + let (carry, result): (bool, u32) = jet::add_32(index, amount); + ensure_zero_bit(carry); + result +} + +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 ensure_input_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::input_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::output_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = get_output_explicit_asset_amount(index); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_input_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), expected)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + assert!(jet::eq_32(jet::current_index(), index)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, collateral_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, collateral_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +fn merge_2_tokens() { + // 2 tokens to merge + 1 input as fee + assert!(jet::eq_32(jet::num_inputs(), 3)); + // 3 outputs: 1 merged token + 1 change + 1 fee + assert!(jet::eq_32(jet::num_outputs(), 3)); + assert!(jet::le_32(jet::current_index(), 1)); + + ensure_input_and_output_script_hash_eq(0); + let script_hash: u256 = get_input_script_hash(0); + assert!(jet::eq_256(script_hash, get_input_script_hash(1))); +} + +fn merge_3_tokens() { + // 3 tokens to merge + 1 input as fee + assert!(jet::eq_32(jet::num_inputs(), 4)); + // 3 outputs: 1 merged token + 1 change + 1 fee + assert!(jet::eq_32(jet::num_outputs(), 3)); + assert!(jet::le_32(jet::current_index(), 2)); + + ensure_input_and_output_script_hash_eq(0); + let script_hash: u256 = get_input_script_hash(0); + assert!(jet::eq_256(script_hash, get_input_script_hash(1))); + assert!(jet::eq_256(script_hash, get_input_script_hash(2))); +} + +fn merge_4_tokens() { + // 4 tokens to merge + 1 input as fee + assert!(jet::eq_32(jet::num_inputs(), 5)); + // 3 outputs: 1 merged token + 1 change + 1 fee + assert!(jet::eq_32(jet::num_outputs(), 3)); + assert!(jet::le_32(jet::current_index(), 3)); + + ensure_input_and_output_script_hash_eq(0); + let script_hash: u256 = get_input_script_hash(0); + assert!(jet::eq_256(script_hash, get_input_script_hash(1))); + assert!(jet::eq_256(script_hash, get_input_script_hash(2))); + assert!(jet::eq_256(script_hash, get_input_script_hash(3))); +} + +/* +* Maker funding path +* Params: +* 1. FILLER_PER_SETTLEMENT_COLLATERAL +* 2. FILLER_PER_SETTLEMENT_ASSET +* 3. FILLER_PER_PRINCIPAL_COLLATERAL +* 4. GRANTOR_SETTLEMENT_PER_DEPOSITED_ASSET +* 5. GRANTOR_COLLATERAL_PER_DEPOSITED_COLLATERAL +* 6. GRANTOR_PER_SETTLEMENT_COLLATERAL +* 7. GRANTOR_PER_SETTLEMENT_ASSET +*/ +fn maker_funding_path(principal_collateral_amount: u64, principal_asset_amount: u64, interest_collateral_amount: u64, interest_asset_amount: u64) { + assert!(jet::eq_32(jet::num_inputs(), 5)); + assert!(jet::eq_32(jet::num_outputs(), 11)); + + let current_time: u32 = ::into(jet::lock_time()); + assert!(jet::lt_32(current_time, param::TAKER_FUNDING_START_TIME)); + + ensure_input_and_output_script_hash_eq(0); + ensure_input_and_output_script_hash_eq(1); + ensure_input_and_output_script_hash_eq(2); + + assert!(jet::le_32(jet::current_index(), 2)); + + let script_hash: u256 = get_output_script_hash(0); + ensure_output_script_hash_eq(1, script_hash); + ensure_output_script_hash_eq(2, script_hash); + ensure_output_script_hash_eq(3, script_hash); + ensure_output_script_hash_eq(4, script_hash); + ensure_output_script_hash_eq(5, script_hash); + + let (collateral_asset_bits, collateral_amount): (u256, u64) = get_output_explicit_asset_amount(3); + let (settlement_asset_bits, settlement_amount): (u256, u64) = get_output_explicit_asset_amount(4); + let filler_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(0)))); + let grantor_collateral_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(1)))); + let grantor_settlement_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(2)))); + assert!(jet::eq_64(filler_token_amount, grantor_collateral_token_amount)); + assert!(jet::eq_64(filler_token_amount, grantor_settlement_token_amount)); + + divmod_eq(principal_asset_amount, param::STRIKE_PRICE, principal_collateral_amount); + + assert!(jet::eq_64(collateral_amount, interest_collateral_amount)); + constraint_percentage(principal_collateral_amount, param::INCENTIVE_BASIS_POINTS, collateral_amount); + + let MAX_BASIS_POINTS: u64 = 10000; + let (carry, asset_incentive_percentage): (bool, u64) = jet::add_64(param::INCENTIVE_BASIS_POINTS, MAX_BASIS_POINTS); + ensure_zero_bit(carry); + + constraint_percentage(principal_asset_amount, asset_incentive_percentage, settlement_amount); + + let (carry, calculated_total_asset_amount): (bool, u64) = jet::add_64(principal_asset_amount, interest_asset_amount); + ensure_zero_bit(carry); + assert!(jet::eq_64(calculated_total_asset_amount, settlement_amount)); + + let (carry, calculated_total_collateral_amount): (bool, u64) = jet::add_64(principal_collateral_amount, interest_collateral_amount); + ensure_zero_bit(carry); + + // Filler token constraints + divmod_eq(calculated_total_collateral_amount, param::FILLER_PER_SETTLEMENT_COLLATERAL, filler_token_amount); + divmod_eq(calculated_total_asset_amount, param::FILLER_PER_SETTLEMENT_ASSET, filler_token_amount); + divmod_eq(principal_collateral_amount, param::FILLER_PER_PRINCIPAL_COLLATERAL, filler_token_amount); + + // Grantor token constraints + divmod_eq(calculated_total_asset_amount, param::GRANTOR_SETTLEMENT_PER_DEPOSITED_ASSET, grantor_settlement_token_amount); + divmod_eq(interest_collateral_amount, param::GRANTOR_COLLATERAL_PER_DEPOSITED_COLLATERAL, grantor_collateral_token_amount); + + divmod_eq(calculated_total_collateral_amount, param::GRANTOR_PER_SETTLEMENT_COLLATERAL, grantor_collateral_token_amount); + // divmod_eq(calculated_total_collateral_amount, param::GRANTOR_PER_SETTLEMENT_COLLATERAL, grantor_settlement_token_amount); // duplicated because of lines 203-204 + + divmod_eq(calculated_total_asset_amount, param::GRANTOR_PER_SETTLEMENT_ASSET, grantor_collateral_token_amount); + // divmod_eq(calculated_total_asset_amount, param::GRANTOR_PER_SETTLEMENT_ASSET, grantor_settlement_token_amount); // duplicated because of lines 203-204 + + assert!(jet::eq_256(param::COLLATERAL_ASSET_ID, collateral_asset_bits)); + assert!(jet::eq_256(param::SETTLEMENT_ASSET_ID, settlement_asset_bits)); + + ensure_output_asset_with_amount_eq(5, param::FILLER_TOKEN_ASSET, filler_token_amount); + ensure_output_asset_with_amount_eq(6, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_collateral_token_amount); + ensure_output_asset_with_amount_eq(7, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_settlement_token_amount); + + ensure_input_asset_eq(3, param::SETTLEMENT_ASSET_ID); + ensure_input_asset_eq(4, param::COLLATERAL_ASSET_ID); + + ensure_output_asset_eq(8, param::COLLATERAL_ASSET_ID); + ensure_output_asset_eq(9, param::SETTLEMENT_ASSET_ID); + ensure_output_asset_eq(10, param::COLLATERAL_ASSET_ID); +} + +fn taker_funding_path(collateral_amount_to_deposit: u64, filler_token_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + assert!(jet::le_32(param::TAKER_FUNDING_START_TIME, current_time)); + assert!(jet::lt_32(current_time, param::TAKER_FUNDING_END_TIME)); + assert!(jet::lt_32(current_time, param::CONTRACT_EXPIRY_TIME)); + + let filler_token_input_index: u32 = 0; + let collateral_input_index: u32 = 1; + + let (collateral_to_covenant_output_index, filler_to_user_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(filler_token_input_index); + + // Check and ensure filler token change + ensure_correct_change_at_index(0, param::FILLER_TOKEN_ASSET, filler_token_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_deposit, param::FILLER_PER_PRINCIPAL_COLLATERAL, filler_token_amount_to_get); + + // Ensure collateral asset and script hash are correct + ensure_output_asset_with_amount_eq(collateral_to_covenant_output_index, param::COLLATERAL_ASSET_ID, collateral_amount_to_deposit); + ensure_output_script_hash_eq(collateral_to_covenant_output_index, expected_current_script_hash); + + ensure_output_asset_with_amount_eq(filler_to_user_output_index, param::FILLER_TOKEN_ASSET, filler_token_amount_to_get); +} + +fn taker_early_termination_path(filler_token_amount_to_return: u64, collateral_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + ensure_one_bit_or(jet::le_32(current_time, param::EARLY_TERMINATION_END_TIME), jet::le_32(param::CONTRACT_EXPIRY_TIME, current_time)); + + let collateral_input_index: u32 = 0; + let filler_token_input_index: u32 = 1; + + let (return_filler_output_index, return_collateral_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::FILLER_PER_PRINCIPAL_COLLATERAL, filler_token_amount_to_return); + + // Ensure filler token transferred to covenant + ensure_output_asset_with_amount_eq(return_filler_output_index, param::FILLER_TOKEN_ASSET, filler_token_amount_to_return); + ensure_output_script_hash_eq(return_filler_output_index, expected_current_script_hash); + + // Ensure collateral transferred to user + ensure_output_asset_with_amount_eq(return_collateral_output_index, param::COLLATERAL_ASSET_ID, collateral_amount_to_get); +} + +fn maker_collateral_termination_path(grantor_collateral_amount_to_burn: u64, collateral_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + ensure_one_bit_or(jet::le_32(current_time, param::EARLY_TERMINATION_END_TIME), jet::le_32(param::CONTRACT_EXPIRY_TIME, current_time)); + + let collateral_input_index: u32 = 0; + let grantor_collateral_token_input_index: u32 = 1; + + let (burn_grantor_collateral_output_index, return_collateral_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::GRANTOR_COLLATERAL_PER_DEPOSITED_COLLATERAL, grantor_collateral_amount_to_burn); + + // Burn grantor collateral token + ensure_output_is_op_return(burn_grantor_collateral_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_collateral_output_index, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_collateral_amount_to_burn); + + // Ensure collateral transferred to user + ensure_output_asset_with_amount_eq(return_collateral_output_index, param::COLLATERAL_ASSET_ID, collateral_amount_to_get); +} + +fn maker_settlement_termination_path(grantor_settlement_amount_to_burn: u64, settlement_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + ensure_one_bit_or(jet::le_32(current_time, param::EARLY_TERMINATION_END_TIME), jet::le_32(param::CONTRACT_EXPIRY_TIME, current_time)); + + let settlement_asset_input_index: u32 = 0; + let grantor_settlement_token_input_index: u32 = 1; + + let (burn_grantor_settlement_output_index, return_settlement_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(settlement_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, settlement_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset amount is correct + divmod_eq(settlement_amount_to_get, param::GRANTOR_SETTLEMENT_PER_DEPOSITED_ASSET, grantor_settlement_amount_to_burn); + + // Burn grantor settlement token + ensure_output_is_op_return(burn_grantor_settlement_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_settlement_output_index, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_settlement_amount_to_burn); + + // Ensure settlement asset transferred to user + ensure_output_asset_with_amount_eq(return_settlement_output_index, param::SETTLEMENT_ASSET_ID, settlement_amount_to_get); +} + +fn ensure_correct_return_at(user_output_index: u32, asset_id: u256, amount_to_get: u64, fee_basis_points: u64) { + match jet::eq_64(fee_basis_points, 0) { + true => ensure_output_asset_with_amount_eq(user_output_index, asset_id, amount_to_get), + false => { + let fee_output_index: u32 = increment_by(user_output_index, 1); + + let (user_asset_bits, user_amount): (u256, u64) = get_output_explicit_asset_amount(user_output_index); + assert!(jet::eq_256(user_asset_bits, asset_id)); + + let (fee_asset_bits, fee_amount): (u256, u64) = get_output_explicit_asset_amount(fee_output_index); + assert!(jet::eq_256(fee_asset_bits, asset_id)); + + let (carry, calculated_total_amount): (bool, u64) = jet::add_64(user_amount, fee_amount); + ensure_zero_bit(carry); + + constraint_percentage(calculated_total_amount, fee_basis_points, fee_amount); + + ensure_output_script_hash_eq(fee_output_index, param::FEE_SCRIPT_HASH); + }, + }; +} + +fn maker_settlement_path(price_at_current_block_height: u64, oracle_sig: Signature, grantor_amount_to_burn: u64, amount_to_get: u64, is_change_needed: bool) { + jet::check_lock_height(param::SETTLEMENT_HEIGHT); + checksig_priceblock(param::ORACLE_PK, param::SETTLEMENT_HEIGHT, price_at_current_block_height, oracle_sig); + + match jet::le_64(price_at_current_block_height, param::STRIKE_PRICE) { + true => { + // Maker gets ALT + let settlement_asset_input_index: u32 = 0; + + let (burn_grantor_settlement_output_index, burn_grantor_collateral_output_index, settlement_output_index): (u32, u32, u32) = match is_change_needed { + true => (1, 2, 3), + false => (0, 1, 2), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(settlement_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset amount is correct + divmod_eq(amount_to_get, param::GRANTOR_PER_SETTLEMENT_ASSET, grantor_amount_to_burn); + + // Burn grantor settlement and collateral tokens + ensure_output_is_op_return(burn_grantor_settlement_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_settlement_output_index, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_amount_to_burn); + ensure_output_is_op_return(burn_grantor_collateral_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_collateral_output_index, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_amount_to_burn); + + // Ensure settlement asset transferred to user + ensure_correct_return_at(settlement_output_index, param::SETTLEMENT_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + false => { + // Maker gets the LBTC + let collateral_input_index: u32 = 0; + + let (burn_grantor_collateral_output_index, burn_grantor_settlement_output_index, collateral_output_index): (u32, u32, u32) = match is_change_needed { + true => (1, 2, 3), + false => (0, 1, 2), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(amount_to_get, param::GRANTOR_PER_SETTLEMENT_COLLATERAL, grantor_amount_to_burn); + + // Burn grantor collateral and settlement tokens + ensure_output_is_op_return(burn_grantor_collateral_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_collateral_output_index, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_amount_to_burn); + ensure_output_is_op_return(burn_grantor_settlement_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_settlement_output_index, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_amount_to_burn); + + // Ensure collateral transferred to user + ensure_correct_return_at(collateral_output_index, param::COLLATERAL_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + } +} + +fn taker_settlement_path(price_at_current_block_height: u64, oracle_sig: Signature, filler_amount_to_burn: u64, amount_to_get: u64, is_change_needed: bool) { + jet::check_lock_height(param::SETTLEMENT_HEIGHT); + checksig_priceblock(param::ORACLE_PK, param::SETTLEMENT_HEIGHT, price_at_current_block_height, oracle_sig); + + match jet::le_64(price_at_current_block_height, param::STRIKE_PRICE) { + true => { + // Taker receives LBTC principal+interest + let collateral_input_index: u32 = 0; + + let (burn_filler_output_index, collateral_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(amount_to_get, param::FILLER_PER_SETTLEMENT_COLLATERAL, filler_amount_to_burn); + + // Burn filler token + ensure_output_is_op_return(burn_filler_output_index); + ensure_output_asset_with_amount_eq(burn_filler_output_index, param::FILLER_TOKEN_ASSET, filler_amount_to_burn); + + // Ensure collateral transferred to user + ensure_correct_return_at(collateral_output_index, param::COLLATERAL_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + false => { + // Taker receives ALT + let settlement_asset_input_index: u32 = 0; + + let (burn_filler_output_index, settlement_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(settlement_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset amount is correct + divmod_eq(amount_to_get, param::FILLER_PER_SETTLEMENT_ASSET, filler_amount_to_burn); + + // Burn filler token + ensure_output_is_op_return(burn_filler_output_index); + ensure_output_asset_with_amount_eq(burn_filler_output_index, param::FILLER_TOKEN_ASSET, filler_amount_to_burn); + + // Ensure filler token transferred to user + ensure_correct_return_at(settlement_output_index, param::SETTLEMENT_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + } +} + +fn main() { + let token_branch: Either<(), ()> = witness::TOKEN_BRANCH; + let merge_branch: Either, ()> = witness::MERGE_BRANCH; + + match witness::PATH { + Left(funding_or_settlement: Either, (u64, Signature, u64, u64, bool)>) => match funding_or_settlement { + // Funding branches + Left(funding_params: Either<(u64, u64, u64, u64), (u64, u64, bool)>) => match funding_params { + // Maker funding: (principal_collateral_amount, principal_asset_amount, interest_collateral_amount, interest_asset_amount) + Left(params: (u64, u64, u64, u64)) => { + let (principal_collateral_amount, principal_asset_amount, interest_collateral_amount, interest_asset_amount): (u64, u64, u64, u64) = params; + maker_funding_path(principal_collateral_amount, principal_asset_amount, interest_collateral_amount, interest_asset_amount) + }, + // Taker funding: (collateral_amount_to_deposit, filler_token_amount_to_get, is_change_needed) + Right(params: (u64, u64, bool)) => { + let (collateral_amount_to_deposit, filler_token_amount_to_get, is_change_needed): (u64, u64, bool) = params; + taker_funding_path(collateral_amount_to_deposit, filler_token_amount_to_get, is_change_needed) + }, + }, + // Settlement branches (oracle price attested) + Right(params: (u64, Signature, u64, u64, bool)) => { + let (price_at_current_block_height, oracle_sig, amount_to_burn, amount_to_get, is_change_needed): (u64, Signature, u64, u64, bool) = params; + + match token_branch { + // Maker settlement: burn grantor token + Left(u: ()) => maker_settlement_path(price_at_current_block_height, oracle_sig, amount_to_burn, amount_to_get, is_change_needed), + // Taker settlement: burn filler token + Right(u: ()) => taker_settlement_path(price_at_current_block_height, oracle_sig, amount_to_burn, amount_to_get, is_change_needed), + } + }, + }, + // Termination flows (early termination or post-expiry) or Merge flows + Right(termination_or_maker_or_merge: Either, ()>) => match termination_or_maker_or_merge { + Left(termination_or_maker: Either<(bool, u64, u64), (bool, u64, u64)>) => match termination_or_maker { + // Taker early termination: (is_change_needed, filler_token_amount_to_return, collateral_amount_to_get) + Left(params: (bool, u64, u64)) => { + let (is_change_needed, filler_token_amount_to_return, collateral_amount_to_get): (bool, u64, u64) = params; + taker_early_termination_path(filler_token_amount_to_return, collateral_amount_to_get, is_change_needed) + }, + // Maker termination (burn grantor token): choose collateral vs settlement token via token_branch + Right(params: (bool, u64, u64)) => { + let (is_change_needed, grantor_token_amount_to_burn, amount_to_get): (bool, u64, u64) = params; + + match token_branch { + // Burn grantor collateral token -> receive collateral + Left(u: ()) => maker_collateral_termination_path(grantor_token_amount_to_burn, amount_to_get, is_change_needed), + // Burn grantor settlement token -> receive settlement asset + Right(u: ()) => maker_settlement_termination_path(grantor_token_amount_to_burn, amount_to_get, is_change_needed), + } + }, + }, + Right(u: ()) => { + // Merge tokens based on MERGE_BRANCH discriminator + match merge_branch { + Left(left_or_right: Either<(), ()>) => match left_or_right { + Left(u: ()) => merge_2_tokens(), + Right(u: ()) => merge_3_tokens(), + }, + Right(u: ()) => merge_4_tokens(), + } + }, + }, + } + +} diff --git a/crates/simplex/tests/ui_simfs/option_offer.simf b/crates/simplex/tests/ui_simfs/option_offer.simf new file mode 100644 index 0000000..5cb2108 --- /dev/null +++ b/crates/simplex/tests/ui_simfs/option_offer.simf @@ -0,0 +1,213 @@ +/* + * Option Offer + * + * A covenant that allows a user to deposit collateral and premium assets, + * and have a counterparty swap settlement asset for both. + * The user can withdraw accumulated settlement asset at any time (with signature). + * After expiry, the user can reclaim any remaining collateral and premium (with signature). + * + * Paths: + * 1. Exercise: Counterparty swaps settlement asset for collateral + premium (no time restriction, optional change) + * 2. Withdraw: User withdraws settlement asset (no time restriction, signature required, full amount) + * 3. Expiry: User reclaims collateral + premium (after expiry, signature required, full amount) + * + * Constraints: + * settlement_amount = COLLATERAL_PER_CONTRACT * collateral_amount + * premium_amount = PREMIUM_PER_COLLATERAL * collateral_amount + */ + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +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_zero_bit(bit: bool) { + assert!(jet::eq_1(::into(bit), 0)); +} + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = get_output_explicit_asset_amount(index); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +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 check_user_signature(sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((param::USER_PUBKEY, msg), sig); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, asset_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, asset_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +/* + * Exercise Path + * + * Counterparty swaps settlement asset for collateral + premium. + * No time restriction - works before and after expiry. + * + * Constraints: + * settlement_amount = COLLATERAL_PER_CONTRACT * collateral_amount + * premium_amount = PREMIUM_PER_COLLATERAL * collateral_amount + * + * Layout: + * + * Both: + * Input[0]: Collateral from covenant + * Input[1]: Premium from covenant + * + * With change (partial swap): + * Output[0]: Collateral change → covenant + * Output[1]: Premium change → covenant + * Output[2]: Settlement asset → covenant + * Output[3]: Collateral → counterparty + * Output[4]: Premium → counterparty + * + * Without change (full swap): + * Output[0]: Settlement asset → covenant + * Output[1]: Collateral → counterparty + * Output[2]: Premium → counterparty + */ +fn exercise_path(collateral_amount: u64, is_change_needed: bool) { + assert!(jet::le_32(jet::current_index(), 1)); + + let expected_covenant_script_hash: u256 = get_input_script_hash(0); + + assert!(jet::eq_256(get_input_script_hash(1), expected_covenant_script_hash)); + + let premium_amount_u128: u128 = jet::multiply_64(collateral_amount, param::PREMIUM_PER_COLLATERAL); + let (left_part, premium_amount): (u64, u64) = dbg!(::into(premium_amount_u128)); + assert!(jet::eq_64(left_part, 0)); + + // Check collateral changes + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount, expected_covenant_script_hash, is_change_needed); + ensure_correct_change_at_index(1, param::PREMIUM_ASSET_ID, premium_amount, expected_covenant_script_hash, is_change_needed); + + let (settlement_output_index, collateral_output_index, premium_output_index): (u32, u32, u32) = match is_change_needed { + true => (2, 3, 4), + false => (0, 1, 2), + }; + + ensure_output_script_hash_eq(settlement_output_index, expected_covenant_script_hash); + + let (output_asset, settlement_amount): (u256, u64) = get_output_explicit_asset_amount(settlement_output_index); + assert!(jet::eq_256(output_asset, param::SETTLEMENT_ASSET_ID)); + + divmod_eq(settlement_amount, param::COLLATERAL_PER_CONTRACT, collateral_amount); + + ensure_output_asset_with_amount_eq(collateral_output_index, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(premium_output_index, param::PREMIUM_ASSET_ID, premium_amount); +} + +/* + * Withdraw Path + * + * User withdraws accumulated settlement asset. + * No time restriction. + * Requires signature from USER_PUBKEY. + * No change - full withdrawal only. + * + * Layout: + * Input[0]: Settlement asset from covenant + * Output[0]: Settlement asset → user (any address) + */ +fn withdraw_path(sig: Signature) { + assert!(jet::eq_32(jet::current_index(), 0)); + + let (input_asset, input_amount): (u256, u64) = get_input_explicit_asset_amount(0); + assert!(jet::eq_256(input_asset, param::SETTLEMENT_ASSET_ID)); + + check_user_signature(sig); + + ensure_output_asset_with_amount_eq(0, param::SETTLEMENT_ASSET_ID, input_amount); +} + +/* + * Expiry Path + * + * User reclaims remaining collateral and premium after expiry. + * Only allowed after EXPIRY_TIME. + * Requires signature from USER_PUBKEY. + * No change - full reclaim only. + * + * Layout: + * Input[0]: Collateral from covenant + * Input[1]: Premium from covenant + * Output[0]: Collateral → user (any address) + * Output[1]: Premium → user (any address) + */ +fn expiry_path(sig: Signature) { + jet::check_lock_time(param::EXPIRY_TIME); + + assert!(jet::le_32(jet::current_index(), 1)); + + let expected_covenant_script_hash: u256 = get_input_script_hash(0); + + assert!(jet::eq_256(get_input_script_hash(1), expected_covenant_script_hash)); + + let (collateral_asset, collateral_amount): (u256, u64) = get_input_explicit_asset_amount(0); + assert!(jet::eq_256(collateral_asset, param::COLLATERAL_ASSET_ID)); + + let (premium_asset, premium_amount): (u256, u64) = get_input_explicit_asset_amount(1); + assert!(jet::eq_256(premium_asset, param::PREMIUM_ASSET_ID)); + + check_user_signature(sig); + + ensure_output_asset_with_amount_eq(0, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(1, param::PREMIUM_ASSET_ID, premium_amount); +} + +fn main() { + match witness::PATH { + Left(params: (u64, bool)) => { + let (collateral_amount, is_change_needed): (u64, bool) = params; + exercise_path(collateral_amount, is_change_needed) + }, + Right(withdraw_or_expiry: Either) => match withdraw_or_expiry { + Left(sig: Signature) => withdraw_path(sig), + Right(sig: Signature) => expiry_path(sig), + }, + } +} diff --git a/example/simf/options.simf b/crates/simplex/tests/ui_simfs/options.simf similarity index 100% rename from example/simf/options.simf rename to crates/simplex/tests/ui_simfs/options.simf diff --git a/crates/simplex/tests/ui_simfs/simple_storage.simf b/crates/simplex/tests/ui_simfs/simple_storage.simf new file mode 100644 index 0000000..7ae6c41 --- /dev/null +++ b/crates/simplex/tests/ui_simfs/simple_storage.simf @@ -0,0 +1,102 @@ +/* + * 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) = 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 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; + enforce_stage_checks(index, witness::NEW_VALUE); + + checksig(param::USER, witness::USER_SIGNATURE) +} diff --git a/simplex/crates/macros-core/Cargo.toml b/crates/template-gen/Cargo.toml similarity index 83% rename from simplex/crates/macros-core/Cargo.toml rename to crates/template-gen/Cargo.toml index d50228a..51ab827 100644 --- a/simplex/crates/macros-core/Cargo.toml +++ b/crates/template-gen/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "simplex-macros-core" +name = "simplex-template-gen-core" description = "Macro support core for Simplex, the Rust SimplicityHl toolkit. Not intended to be used directly." version = "0.1.0" license.workspace = true @@ -14,13 +14,11 @@ serde = ["bincode", "dep:serde"] default = ["bincode", "serde"] [dependencies] -simplicityhl = { workspace = true } -simplex-test = { workspace = true } +simplex-macros-core = { workspace = true } thiserror = { workspace = true } -proc-macro-error = { version = "1.0" } -proc-macro2 = { version = "1.0.106", features = ["span-locations"] } 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" } serde = { version = "1.0.228", optional = true } pathdiff = { version = "0.2.3" } diff --git a/crates/template-gen/src/env.rs b/crates/template-gen/src/env.rs new file mode 100644 index 0000000..0afb76a --- /dev/null +++ b/crates/template-gen/src/env.rs @@ -0,0 +1,256 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use simplex_macros_core::attr::SimfContent; +use simplex_macros_core::attr::codegen::{ + convert_contract_name_to_contract_module, convert_contract_name_to_contract_source_const, + convert_contract_name_to_struct_name, +}; +use std::collections::HashMap; +use std::io::Write; +use std::path::{Component, Path, PathBuf}; +use std::{fs, io}; + +#[derive(thiserror::Error, Debug)] +pub enum CodeGeneratorError { + #[error("IO error: {0}")] + Io(#[from] io::Error), + + #[error("Failed to extract content from path, err: '{0}'")] + FailedToExtractContent(io::Error), + + #[error("Failed to generate file: {0}")] + GenerationFailed(String), + + #[error( + "Failed to resolve correct relative path for include_simf! macro, cwd: '{cwd:?}', simf_file: '{simf_file:?}'" + )] + FailedToFindCorrectRelativePath { cwd: PathBuf, simf_file: PathBuf }, + + #[error("Failed to find prefix for a file: {0}")] + NoBasePathForGeneration(#[from] std::path::StripPrefixError), +} + +pub struct CodeGenerator {} + +struct FileDescriptor { + simf_content: SimfContent, + simf_file: PathBuf, + cwd: PathBuf, +} + +type _ContractModName = String; +type _FolderName = String; +type _ContractName = String; + +#[derive(Debug, Default)] +struct _TreeNode { + files: Vec, + folders: HashMap<_FolderName, _TreeNode>, +} + +impl<'b> CodeGenerator { + pub fn generate_files( + cwd: impl AsRef, + out_dir: impl AsRef, + simfs: &[impl AsRef], + ) -> Result<(), CodeGeneratorError> { + let _ = Self::_generate_files(cwd, out_dir, simfs)?; + + Ok(()) + } + + pub fn generate_artifacts_mod( + out_dir_name: impl AsRef, + cwd: impl AsRef, + base_path: impl AsRef, + out_dir: impl AsRef, + simfs: &[impl AsRef], + ) -> Result<(), CodeGeneratorError> { + let out_dir = dbg!(out_dir.as_ref().join(out_dir_name.as_ref())); + + let tree = dbg!(Self::_build_directory_tree(simfs, &base_path)?); + Self::_generate_tree_file_structure(cwd.as_ref(), &out_dir, tree)?; + + Ok(()) + } +} + +impl<'b> CodeGenerator { + fn _generate_files( + cwd: impl AsRef, + out_dir: impl AsRef, + simfs: &[impl AsRef], + ) -> Result, CodeGeneratorError> { + let out_dir = out_dir.as_ref(); + let cwd = cwd.as_ref(); + + fs::create_dir_all(out_dir)?; + let mut module_files = Vec::with_capacity(simfs.len()); + + for simf_file_path in simfs { + let mod_name = Self::_generate_file(cwd, out_dir, simf_file_path)?; + module_files.push(mod_name); + } + Ok(module_files) + } + + fn _generate_tree_file_structure( + cwd: &Path, + out_dir: &Path, + path_tree: _TreeNode, + ) -> Result, CodeGeneratorError> { + let mut mod_filenames = Self::_generate_files(cwd, &out_dir, &path_tree.files)?; + for (folder_name, tree_node) in path_tree.folders.into_iter() { + Self::_generate_tree_file_structure(cwd, &out_dir.join(&folder_name), tree_node)?; + mod_filenames.push(folder_name); + } + Self::_generate_mod_rs(&out_dir, &mod_filenames)?; + Ok(mod_filenames) + } + + fn _generate_mod_rs( + out_dir: impl AsRef, + simfs_mod_name: &[_ContractModName], + ) -> Result<(), CodeGeneratorError> { + let out_dir = out_dir.as_ref(); + let output_file = out_dir.join("mod.rs"); + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&output_file)?; + let simfs_mod_name = simfs_mod_name.iter().map(|x| format_ident!("{x}")).collect::>(); + let code = quote! { + #(pub mod #simfs_mod_name);*; + }; + Self::expand_file(code, &mut file)?; + Ok(()) + } + + fn _build_directory_tree( + paths: &[impl AsRef], + base: impl AsRef, + ) -> Result<_TreeNode, CodeGeneratorError> { + let mut root = _TreeNode::default(); + + for path in paths { + let path = path.as_ref(); + + let relative_path = path + .strip_prefix(base.as_ref()) + .map_err(CodeGeneratorError::NoBasePathForGeneration)?; + + let components: Vec<_> = relative_path + .components() + .filter_map(|c| { + if let Component::Normal(name) = c { + Some(name) + } else { + None + } + }) + .collect(); + + let mut current_node = &mut root; + let components_len = components.len(); + + for (i, name) in components.into_iter().enumerate() { + let is_file = i == components_len - 1; + if is_file { + current_node.files.push(path.to_path_buf()); + } else { + let folder_name = name.to_string_lossy().into_owned(); + current_node = current_node.folders.entry(folder_name).or_default(); + } + } + } + + Ok(root) + } + + fn _generate_file( + cwd: impl AsRef, + out_dir: impl AsRef, + simf_file_path: impl AsRef, + ) -> Result<_ContractModName, CodeGeneratorError> { + let path_buf = PathBuf::from(simf_file_path.as_ref()); + let simf_content = + SimfContent::extract_content_from_path(&path_buf).map_err(CodeGeneratorError::FailedToExtractContent)?; + + dbg!(cwd.as_ref()); + fs::create_dir_all(&out_dir)?; + let output_file = out_dir.as_ref().join(format!("{}.rs", &simf_content.contract_name)); + let contract_name = simf_content.contract_name.clone(); + + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&output_file)?; + let code = Self::generate_simf_binding_code(FileDescriptor { + simf_content, + simf_file: path_buf, + cwd: cwd.as_ref().to_path_buf(), + })?; + Self::expand_file(code, &mut file)?; + Ok(contract_name) + } + + fn expand_file(code: TokenStream, buf: &mut dyn Write) -> Result<(), CodeGeneratorError> { + let file: syn::File = syn::parse2(code).map_err(|e| CodeGeneratorError::GenerationFailed(e.to_string()))?; + let prettystr = prettyplease::unparse(&file); + buf.write_all(prettystr.as_bytes())?; + buf.flush()?; + Ok(()) + } + + fn generate_simf_binding_code(file_descriptor: FileDescriptor) -> Result { + let contract_name = &file_descriptor.simf_content.contract_name; + let program_name = { + let base_name = convert_contract_name_to_struct_name(contract_name); + format_ident!("{base_name}Program") + }; + 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 pathdiff = pathdiff::diff_paths(&file_descriptor.simf_file, &file_descriptor.cwd).ok_or( + CodeGeneratorError::FailedToFindCorrectRelativePath { + cwd: file_descriptor.cwd, + simf_file: file_descriptor.simf_file, + }, + )?; + let pathdiff = format!("{}", pathdiff.display()); + + let code = quote! { + use simplex::simplex_macros::include_simf; + use simplex::simplex_sdk::program::{ArgumentsTrait, Program}; + use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; + + pub struct #program_name { + program: Program, + } + + impl #program_name { + pub const SOURCE: &'static str = #include_simf_module::#include_simf_source_const; + + pub fn new(public_key: XOnlyPublicKey, arguments: impl ArgumentsTrait + 'static) -> Self { + Self { + program: Program::new(Self::SOURCE, public_key, Box::new(arguments)), + } + } + + pub fn get_program(&self) -> &Program { + &self.program + } + + pub fn get_program_mut(&mut self) -> &mut Program { + &mut self.program + } + } + + include_simf!(#pathdiff); + }; + + Ok(code) + } +} diff --git a/crates/template-gen/src/lib.rs b/crates/template-gen/src/lib.rs new file mode 100644 index 0000000..cc7e391 --- /dev/null +++ b/crates/template-gen/src/lib.rs @@ -0,0 +1,23 @@ +#![warn(clippy::all, clippy::pedantic)] + +mod env; +pub use env::CodeGeneratorError; + +pub fn expand_only_files( + cwd: impl AsRef, + outdir: impl AsRef, + simfs: &[impl AsRef], +) -> Result<(), CodeGeneratorError> { + env::CodeGenerator::generate_files(cwd, outdir, simfs) +} + +pub fn expand_files_with_nested_dirs( + cwd: impl AsRef, + base_dir: impl AsRef, + outdir: impl AsRef, + simfs: &[impl AsRef], +) -> Result<(), CodeGeneratorError> { + const ARTIFACTS_DIR_NAME: &str = "artifacts"; + + env::CodeGenerator::generate_artifacts_mod(ARTIFACTS_DIR_NAME, cwd, base_dir, outdir, simfs) +} diff --git a/simplex/crates/test/Cargo.toml b/crates/test/Cargo.toml similarity index 100% rename from simplex/crates/test/Cargo.toml rename to crates/test/Cargo.toml diff --git a/simplex/crates/test/src/common.rs b/crates/test/src/common.rs similarity index 100% rename from simplex/crates/test/src/common.rs rename to crates/test/src/common.rs diff --git a/simplex/crates/test/src/error.rs b/crates/test/src/error.rs similarity index 100% rename from simplex/crates/test/src/error.rs rename to crates/test/src/error.rs diff --git a/simplex/crates/test/src/lib.rs b/crates/test/src/lib.rs similarity index 100% rename from simplex/crates/test/src/lib.rs rename to crates/test/src/lib.rs diff --git a/simplex/crates/test/src/testing/config.rs b/crates/test/src/testing/config.rs similarity index 100% rename from simplex/crates/test/src/testing/config.rs rename to crates/test/src/testing/config.rs diff --git a/simplex/crates/test/src/testing/mod.rs b/crates/test/src/testing/mod.rs similarity index 99% rename from simplex/crates/test/src/testing/mod.rs rename to crates/test/src/testing/mod.rs index 8965b46..55afece 100644 --- a/simplex/crates/test/src/testing/mod.rs +++ b/crates/test/src/testing/mod.rs @@ -5,7 +5,6 @@ use crate::TestError; pub use config::*; use electrsd::bitcoind::bitcoincore_rpc::Auth; pub use rpc_provider::*; -use std::io; use std::path::PathBuf; pub struct TestContext { diff --git a/simplex/crates/test/src/testing/rpc_provider.rs b/crates/test/src/testing/rpc_provider.rs similarity index 100% rename from simplex/crates/test/src/testing/rpc_provider.rs rename to crates/test/src/testing/rpc_provider.rs diff --git a/example/Cargo.toml b/example/Cargo.toml deleted file mode 100644 index e1f7989..0000000 --- a/example/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "draft_example" -edition = "2024" -rust-version = "1.90.0" -version = "0.1.0" - -[dependencies] -simplex = { path = "../simplex/crates/simplex" } - -simplicityhl = { git = "https://github.com/ikripaka/SimplicityHL/", branch = "feature/rich-params" } -trybuild = { version = "1.0.115" } -anyhow = { version = "1.0.101" } diff --git a/example/Simplex.toml b/example/Simplex.toml deleted file mode 100644 index 349e228..0000000 --- a/example/Simplex.toml +++ /dev/null @@ -1,8 +0,0 @@ -network = "liquidtestnet" - -[build] -compile_simf = ["simf/*.simf"] -out_dir = "./src/artifacts" - -[test] -elementsd_path = "../simplex/assets/elementsd" diff --git a/example/simplex b/example/simplex deleted file mode 100755 index d414575..0000000 Binary files a/example/simplex and /dev/null differ diff --git a/example/src/artifacts/mod.rs b/example/src/artifacts/mod.rs deleted file mode 100644 index cef7d47..0000000 --- a/example/src/artifacts/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod options; -pub mod p2pk; diff --git a/example/tests/draft_test.rs b/example/tests/draft_test.rs deleted file mode 100644 index 16cdc63..0000000 --- a/example/tests/draft_test.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[simplex::simplex_macros::test] -fn test_invocation_tx_tracking(context: simplex::simplex_test::TestContext) -> anyhow::Result<()> { - todo!() -} diff --git a/example/Cargo.lock b/examples/basic/Cargo.lock similarity index 95% rename from example/Cargo.lock rename to examples/basic/Cargo.lock index 532fb85..ae5b5a9 100644 --- a/example/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -96,7 +96,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -421,7 +421,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -431,7 +431,6 @@ dependencies = [ "anyhow", "simplex", "simplicityhl", - "trybuild", ] [[package]] @@ -631,12 +630,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 = "hashbrown" version = "0.15.5" @@ -1111,12 +1104,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1160,31 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "syn", ] [[package]] @@ -1630,7 +1593,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1704,23 +1667,19 @@ name = "simplex-macros" version = "0.1.0" dependencies = [ "simplex-macros-core", - "syn 2.0.117", + "syn", ] [[package]] name = "simplex-macros-core" version = "0.1.0" dependencies = [ - "pathdiff", - "prettyplease", - "proc-macro-error", "proc-macro2", "quote", "serde", "simplex-test", "simplicityhl", - "syn 2.0.117", - "thiserror", + "syn", ] [[package]] @@ -1766,7 +1725,7 @@ dependencies = [ "simplex-sdk", "simplicityhl", "thiserror", - "toml 0.9.12+spec-1.1.0", + "toml", ] [[package]] @@ -1800,7 +1759,7 @@ dependencies = [ [[package]] name = "simplicityhl" version = "0.4.1" -source = "git+https://github.com/ikripaka/SimplicityHL/?branch=feature%2Frich-params#77755254414093ff0e1b51beedbc27367745388e" +source = "git+https://github.com/BlockstreamResearch/SimplicityHL.git?rev=568b462#568b4621d6145cd97dce68a3f3428c7eb85306b6" dependencies = [ "base64 0.21.7", "chumsky", @@ -1867,16 +1826,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -1905,15 +1854,9 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] -[[package]] -name = "target-triple" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" - [[package]] name = "tempfile" version = "3.26.0" @@ -1927,15 +1870,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "2.0.18" @@ -1953,7 +1887,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2014,22 +1948,7 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml" -version = "1.0.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", "winnow", @@ -2044,15 +1963,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_datetime" -version = "1.0.0+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_parser" version = "1.0.9+spec-1.1.0" @@ -2132,7 +2042,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2150,21 +2060,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "trybuild" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c635f0191bd3a2941013e5062667100969f8c4e9cd787c14f977265d73616e" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml 1.0.3+spec-1.1.0", -] - [[package]] name = "typenum" version = "1.19.0" @@ -2331,7 +2226,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -2425,15 +2320,6 @@ dependencies = [ "rustix 0.38.44", ] -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "windows-link" version = "0.2.1" @@ -2641,7 +2527,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2657,7 +2543,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2724,7 +2610,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -2745,7 +2631,7 @@ checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2765,7 +2651,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -2805,7 +2691,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml new file mode 100644 index 0000000..a6c9551 --- /dev/null +++ b/examples/basic/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "draft_example" +edition = "2024" +rust-version = "1.90.0" +version = "0.1.0" + +[dependencies] +simplex = { path = "../../crates/simplex" } + +simplicityhl = { git = "https://github.com/BlockstreamResearch/SimplicityHL.git", rev = "568b462" } +anyhow = { version = "1.0.101" } diff --git a/examples/basic/Simplex.toml b/examples/basic/Simplex.toml new file mode 100644 index 0000000..3e97780 --- /dev/null +++ b/examples/basic/Simplex.toml @@ -0,0 +1,9 @@ +network = "liquidtestnet" + +[build] +simf_files = ["*.simf"] +out_dir = "./src" +base_dir = "./simf" + +[test] +elementsd_path = "../../assets/elementsd" diff --git a/examples/basic/simf/another_dir/another_module/bytes32_tr_storage.simf b/examples/basic/simf/another_dir/another_module/bytes32_tr_storage.simf new file mode 100644 index 0000000..0d11b5f --- /dev/null +++ b/examples/basic/simf/another_dir/another_module/bytes32_tr_storage.simf @@ -0,0 +1,66 @@ +/* + * Computes the "State Commitment" — the expected Script PubKey (address) + * for a specific state value. + * + * HOW IT WORKS: + * In Simplicity/Liquid, state is not stored in a dedicated database. Instead, + * it is verified via a "Commitment Scheme" inside the Taproot tree of the UTXO. + * + * This function reconstructs the Taproot structure to validate that the provided + * witness data (state_data) was indeed cryptographically embedded into the + * transaction output that is currently being spent. + * + * LOGIC FLOW: + * 1. Takes state_data (passed via witness at runtime). + * 2. Hashes it as a non-executable TapData leaf. + * 3. Combines it with the current program's CMR (tapleaf_hash). + * 4. Derives the tweaked_key (Internal Key + Merkle Root). + * 5. Returns the final SHA256 script hash (SegWit v1). + * + * USAGE: + * - In main, we verify: CalculatedHash(witness::STATE) == input_script_hash. + * - This assertion proves that the UTXO is "locked" not just by the code, + * but specifically by THIS instance of the state data. + */ + +fn script_hash_for_input_script(state_data: u256) -> u256 { + // This is the bulk of our "compute state commitment" logic from above. + let tap_leaf: u256 = jet::tapleaf_hash(); + let state_ctx1: Ctx8 = jet::tapdata_init(); + let state_ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(state_ctx1, state_data); + let state_leaf: u256 = jet::sha_256_ctx_8_finalize(state_ctx2); + let tap_node: u256 = jet::build_tapbranch(tap_leaf, state_leaf); + + // Compute a taptweak using this. + let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; + let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node); + + // Turn the taptweak into a script hash + let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init(); + let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32 + let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key); + jet::sha_256_ctx_8_finalize(hash_ctx3) +} + +fn main() { + let state_data: u256 = witness::STATE; + let (state1, state2, state3, state4): (u64, u64, u64, u64) = ::into(state_data); + + // Assert that the input is correct, i.e. "load". + assert!(jet::eq_256( + script_hash_for_input_script(state_data), + unwrap(jet::input_script_hash(jet::current_index())) + )); + + // Do a state update (and fail on 64-bit overflow even though we've got 192 other + // bits we could be using..) + let (carry, new_state4): (bool, u64) = jet::increment_64(state4); + assert!(jet::eq_1(::into(carry), 0)); + + let new_state: u256 = <(u64, u64, u64, u64)>::into((state1, state2, state3, new_state4)); + // Assert that the output is correct, i.e. "store". + assert!(jet::eq_256( + script_hash_for_input_script(new_state), + unwrap(jet::output_script_hash(jet::current_index())) + )); +} \ No newline at end of file diff --git a/examples/basic/simf/another_dir/another_module/dual_currency_deposit.simf b/examples/basic/simf/another_dir/another_module/dual_currency_deposit.simf new file mode 100644 index 0000000..e1a460a --- /dev/null +++ b/examples/basic/simf/another_dir/another_module/dual_currency_deposit.simf @@ -0,0 +1,592 @@ +/* + * DCD: Dual Currency Deposit – price-attested settlement and funding windows + * + * Flows implemented: + * - Maker funding: deposit settlement asset and collateral, issue grantor tokens + * - Taker funding: deposit collateral in window and receive filler tokens + * - Settlement: at SETTLEMENT_HEIGHT, oracle Schnorr signature over (height, price) + * selects LBTC vs ALT branch based on price <= STRIKE_PRICE + * - Early/post-expiry termination: taker returns filler; maker burns grantor tokens + * - Merge: consolidate 2/3/4 token UTXOs + * + * All amounts and asset/script invariants are enforced on-chain; time guards use + * fallback locktime and height checks. + * + * Batching discussion: https://github.com/BlockstreamResearch/simplicity-contracts/issues/4 + */ + +// Verify Schnorr signature against SHA256 of (u32 || u64) +fn checksig_priceblock(pk: Pubkey, current_block_height: u32, price_at_current_block_height: u64, sig: Signature) { + let hasher: Ctx8 = jet::sha_256_ctx_8_init(); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, current_block_height); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_8(hasher, price_at_current_block_height); + let msg: u256 = jet::sha_256_ctx_8_finalize(hasher); + jet::bip_0340_verify((pk, msg), sig); +} + +// Signed <= using XOR with 0x8000.. bias: a<=b (signed) iff (a^bias) <= (b^bias) (unsigned) +fn signed_le_u64(a_bits: u64, b_bits: u64) -> bool { + let bias: u64 = 0x8000000000000000; + jet::le_64(jet::xor_64(a_bits, bias), jet::xor_64(b_bits, bias)) +} + +fn signed_lt_u64(a: u64, b: u64) -> bool { + let bias: u64 = 0x8000000000000000; + jet::lt_64(jet::xor_64(a, bias), jet::xor_64(b, bias)) +} + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +/// Assert: base_amount * basis_point_percentage == provided_amount * MAX_BASIS_POINTS +fn constraint_percentage(base_amount: u64, basis_point_percentage: u64, provided_amount: u64) { + let MAX_BASIS_POINTS: u64 = 10000; + + let arg1: u256 = <(u128, u128)>::into((0, jet::multiply_64(base_amount, basis_point_percentage))); + let arg2: u256 = <(u128, u128)>::into((0, jet::multiply_64(provided_amount, MAX_BASIS_POINTS))); + + assert!(jet::eq_256(arg1, arg2)); +} + +fn get_output_script_hash(index: u32) -> u256 { + unwrap(jet::output_script_hash(index)) +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +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_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 ensure_one_bit_or(bit1: bool, bit2: bool) { + assert!( + jet::eq_1( + ::into(jet::or_1(::into(bit1), ::into(bit2))), + 1 + ) + ); +} + +fn increment_by(index: u32, amount: u32) -> u32 { + let (carry, result): (bool, u32) = jet::add_32(index, amount); + ensure_zero_bit(carry); + result +} + +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 ensure_input_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::input_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::output_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = get_output_explicit_asset_amount(index); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_input_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), expected)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + assert!(jet::eq_32(jet::current_index(), index)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, collateral_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, collateral_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +fn merge_2_tokens() { + // 2 tokens to merge + 1 input as fee + assert!(jet::eq_32(jet::num_inputs(), 3)); + // 3 outputs: 1 merged token + 1 change + 1 fee + assert!(jet::eq_32(jet::num_outputs(), 3)); + assert!(jet::le_32(jet::current_index(), 1)); + + ensure_input_and_output_script_hash_eq(0); + let script_hash: u256 = get_input_script_hash(0); + assert!(jet::eq_256(script_hash, get_input_script_hash(1))); +} + +fn merge_3_tokens() { + // 3 tokens to merge + 1 input as fee + assert!(jet::eq_32(jet::num_inputs(), 4)); + // 3 outputs: 1 merged token + 1 change + 1 fee + assert!(jet::eq_32(jet::num_outputs(), 3)); + assert!(jet::le_32(jet::current_index(), 2)); + + ensure_input_and_output_script_hash_eq(0); + let script_hash: u256 = get_input_script_hash(0); + assert!(jet::eq_256(script_hash, get_input_script_hash(1))); + assert!(jet::eq_256(script_hash, get_input_script_hash(2))); +} + +fn merge_4_tokens() { + // 4 tokens to merge + 1 input as fee + assert!(jet::eq_32(jet::num_inputs(), 5)); + // 3 outputs: 1 merged token + 1 change + 1 fee + assert!(jet::eq_32(jet::num_outputs(), 3)); + assert!(jet::le_32(jet::current_index(), 3)); + + ensure_input_and_output_script_hash_eq(0); + let script_hash: u256 = get_input_script_hash(0); + assert!(jet::eq_256(script_hash, get_input_script_hash(1))); + assert!(jet::eq_256(script_hash, get_input_script_hash(2))); + assert!(jet::eq_256(script_hash, get_input_script_hash(3))); +} + +/* +* Maker funding path +* Params: +* 1. FILLER_PER_SETTLEMENT_COLLATERAL +* 2. FILLER_PER_SETTLEMENT_ASSET +* 3. FILLER_PER_PRINCIPAL_COLLATERAL +* 4. GRANTOR_SETTLEMENT_PER_DEPOSITED_ASSET +* 5. GRANTOR_COLLATERAL_PER_DEPOSITED_COLLATERAL +* 6. GRANTOR_PER_SETTLEMENT_COLLATERAL +* 7. GRANTOR_PER_SETTLEMENT_ASSET +*/ +fn maker_funding_path(principal_collateral_amount: u64, principal_asset_amount: u64, interest_collateral_amount: u64, interest_asset_amount: u64) { + assert!(jet::eq_32(jet::num_inputs(), 5)); + assert!(jet::eq_32(jet::num_outputs(), 11)); + + let current_time: u32 = ::into(jet::lock_time()); + assert!(jet::lt_32(current_time, param::TAKER_FUNDING_START_TIME)); + + ensure_input_and_output_script_hash_eq(0); + ensure_input_and_output_script_hash_eq(1); + ensure_input_and_output_script_hash_eq(2); + + assert!(jet::le_32(jet::current_index(), 2)); + + let script_hash: u256 = get_output_script_hash(0); + ensure_output_script_hash_eq(1, script_hash); + ensure_output_script_hash_eq(2, script_hash); + ensure_output_script_hash_eq(3, script_hash); + ensure_output_script_hash_eq(4, script_hash); + ensure_output_script_hash_eq(5, script_hash); + + let (collateral_asset_bits, collateral_amount): (u256, u64) = get_output_explicit_asset_amount(3); + let (settlement_asset_bits, settlement_amount): (u256, u64) = get_output_explicit_asset_amount(4); + let filler_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(0)))); + let grantor_collateral_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(1)))); + let grantor_settlement_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(2)))); + assert!(jet::eq_64(filler_token_amount, grantor_collateral_token_amount)); + assert!(jet::eq_64(filler_token_amount, grantor_settlement_token_amount)); + + divmod_eq(principal_asset_amount, param::STRIKE_PRICE, principal_collateral_amount); + + assert!(jet::eq_64(collateral_amount, interest_collateral_amount)); + constraint_percentage(principal_collateral_amount, param::INCENTIVE_BASIS_POINTS, collateral_amount); + + let MAX_BASIS_POINTS: u64 = 10000; + let (carry, asset_incentive_percentage): (bool, u64) = jet::add_64(param::INCENTIVE_BASIS_POINTS, MAX_BASIS_POINTS); + ensure_zero_bit(carry); + + constraint_percentage(principal_asset_amount, asset_incentive_percentage, settlement_amount); + + let (carry, calculated_total_asset_amount): (bool, u64) = jet::add_64(principal_asset_amount, interest_asset_amount); + ensure_zero_bit(carry); + assert!(jet::eq_64(calculated_total_asset_amount, settlement_amount)); + + let (carry, calculated_total_collateral_amount): (bool, u64) = jet::add_64(principal_collateral_amount, interest_collateral_amount); + ensure_zero_bit(carry); + + // Filler token constraints + divmod_eq(calculated_total_collateral_amount, param::FILLER_PER_SETTLEMENT_COLLATERAL, filler_token_amount); + divmod_eq(calculated_total_asset_amount, param::FILLER_PER_SETTLEMENT_ASSET, filler_token_amount); + divmod_eq(principal_collateral_amount, param::FILLER_PER_PRINCIPAL_COLLATERAL, filler_token_amount); + + // Grantor token constraints + divmod_eq(calculated_total_asset_amount, param::GRANTOR_SETTLEMENT_PER_DEPOSITED_ASSET, grantor_settlement_token_amount); + divmod_eq(interest_collateral_amount, param::GRANTOR_COLLATERAL_PER_DEPOSITED_COLLATERAL, grantor_collateral_token_amount); + + divmod_eq(calculated_total_collateral_amount, param::GRANTOR_PER_SETTLEMENT_COLLATERAL, grantor_collateral_token_amount); + // divmod_eq(calculated_total_collateral_amount, param::GRANTOR_PER_SETTLEMENT_COLLATERAL, grantor_settlement_token_amount); // duplicated because of lines 203-204 + + divmod_eq(calculated_total_asset_amount, param::GRANTOR_PER_SETTLEMENT_ASSET, grantor_collateral_token_amount); + // divmod_eq(calculated_total_asset_amount, param::GRANTOR_PER_SETTLEMENT_ASSET, grantor_settlement_token_amount); // duplicated because of lines 203-204 + + assert!(jet::eq_256(param::COLLATERAL_ASSET_ID, collateral_asset_bits)); + assert!(jet::eq_256(param::SETTLEMENT_ASSET_ID, settlement_asset_bits)); + + ensure_output_asset_with_amount_eq(5, param::FILLER_TOKEN_ASSET, filler_token_amount); + ensure_output_asset_with_amount_eq(6, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_collateral_token_amount); + ensure_output_asset_with_amount_eq(7, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_settlement_token_amount); + + ensure_input_asset_eq(3, param::SETTLEMENT_ASSET_ID); + ensure_input_asset_eq(4, param::COLLATERAL_ASSET_ID); + + ensure_output_asset_eq(8, param::COLLATERAL_ASSET_ID); + ensure_output_asset_eq(9, param::SETTLEMENT_ASSET_ID); + ensure_output_asset_eq(10, param::COLLATERAL_ASSET_ID); +} + +fn taker_funding_path(collateral_amount_to_deposit: u64, filler_token_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + assert!(jet::le_32(param::TAKER_FUNDING_START_TIME, current_time)); + assert!(jet::lt_32(current_time, param::TAKER_FUNDING_END_TIME)); + assert!(jet::lt_32(current_time, param::CONTRACT_EXPIRY_TIME)); + + let filler_token_input_index: u32 = 0; + let collateral_input_index: u32 = 1; + + let (collateral_to_covenant_output_index, filler_to_user_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(filler_token_input_index); + + // Check and ensure filler token change + ensure_correct_change_at_index(0, param::FILLER_TOKEN_ASSET, filler_token_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_deposit, param::FILLER_PER_PRINCIPAL_COLLATERAL, filler_token_amount_to_get); + + // Ensure collateral asset and script hash are correct + ensure_output_asset_with_amount_eq(collateral_to_covenant_output_index, param::COLLATERAL_ASSET_ID, collateral_amount_to_deposit); + ensure_output_script_hash_eq(collateral_to_covenant_output_index, expected_current_script_hash); + + ensure_output_asset_with_amount_eq(filler_to_user_output_index, param::FILLER_TOKEN_ASSET, filler_token_amount_to_get); +} + +fn taker_early_termination_path(filler_token_amount_to_return: u64, collateral_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + ensure_one_bit_or(jet::le_32(current_time, param::EARLY_TERMINATION_END_TIME), jet::le_32(param::CONTRACT_EXPIRY_TIME, current_time)); + + let collateral_input_index: u32 = 0; + let filler_token_input_index: u32 = 1; + + let (return_filler_output_index, return_collateral_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::FILLER_PER_PRINCIPAL_COLLATERAL, filler_token_amount_to_return); + + // Ensure filler token transferred to covenant + ensure_output_asset_with_amount_eq(return_filler_output_index, param::FILLER_TOKEN_ASSET, filler_token_amount_to_return); + ensure_output_script_hash_eq(return_filler_output_index, expected_current_script_hash); + + // Ensure collateral transferred to user + ensure_output_asset_with_amount_eq(return_collateral_output_index, param::COLLATERAL_ASSET_ID, collateral_amount_to_get); +} + +fn maker_collateral_termination_path(grantor_collateral_amount_to_burn: u64, collateral_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + ensure_one_bit_or(jet::le_32(current_time, param::EARLY_TERMINATION_END_TIME), jet::le_32(param::CONTRACT_EXPIRY_TIME, current_time)); + + let collateral_input_index: u32 = 0; + let grantor_collateral_token_input_index: u32 = 1; + + let (burn_grantor_collateral_output_index, return_collateral_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::GRANTOR_COLLATERAL_PER_DEPOSITED_COLLATERAL, grantor_collateral_amount_to_burn); + + // Burn grantor collateral token + ensure_output_is_op_return(burn_grantor_collateral_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_collateral_output_index, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_collateral_amount_to_burn); + + // Ensure collateral transferred to user + ensure_output_asset_with_amount_eq(return_collateral_output_index, param::COLLATERAL_ASSET_ID, collateral_amount_to_get); +} + +fn maker_settlement_termination_path(grantor_settlement_amount_to_burn: u64, settlement_amount_to_get: u64, is_change_needed: bool) { + let current_time: u32 = ::into(jet::lock_time()); + ensure_one_bit_or(jet::le_32(current_time, param::EARLY_TERMINATION_END_TIME), jet::le_32(param::CONTRACT_EXPIRY_TIME, current_time)); + + let settlement_asset_input_index: u32 = 0; + let grantor_settlement_token_input_index: u32 = 1; + + let (burn_grantor_settlement_output_index, return_settlement_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(settlement_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, settlement_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset amount is correct + divmod_eq(settlement_amount_to_get, param::GRANTOR_SETTLEMENT_PER_DEPOSITED_ASSET, grantor_settlement_amount_to_burn); + + // Burn grantor settlement token + ensure_output_is_op_return(burn_grantor_settlement_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_settlement_output_index, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_settlement_amount_to_burn); + + // Ensure settlement asset transferred to user + ensure_output_asset_with_amount_eq(return_settlement_output_index, param::SETTLEMENT_ASSET_ID, settlement_amount_to_get); +} + +fn ensure_correct_return_at(user_output_index: u32, asset_id: u256, amount_to_get: u64, fee_basis_points: u64) { + match jet::eq_64(fee_basis_points, 0) { + true => ensure_output_asset_with_amount_eq(user_output_index, asset_id, amount_to_get), + false => { + let fee_output_index: u32 = increment_by(user_output_index, 1); + + let (user_asset_bits, user_amount): (u256, u64) = get_output_explicit_asset_amount(user_output_index); + assert!(jet::eq_256(user_asset_bits, asset_id)); + + let (fee_asset_bits, fee_amount): (u256, u64) = get_output_explicit_asset_amount(fee_output_index); + assert!(jet::eq_256(fee_asset_bits, asset_id)); + + let (carry, calculated_total_amount): (bool, u64) = jet::add_64(user_amount, fee_amount); + ensure_zero_bit(carry); + + constraint_percentage(calculated_total_amount, fee_basis_points, fee_amount); + + ensure_output_script_hash_eq(fee_output_index, param::FEE_SCRIPT_HASH); + }, + }; +} + +fn maker_settlement_path(price_at_current_block_height: u64, oracle_sig: Signature, grantor_amount_to_burn: u64, amount_to_get: u64, is_change_needed: bool) { + jet::check_lock_height(param::SETTLEMENT_HEIGHT); + checksig_priceblock(param::ORACLE_PK, param::SETTLEMENT_HEIGHT, price_at_current_block_height, oracle_sig); + + match jet::le_64(price_at_current_block_height, param::STRIKE_PRICE) { + true => { + // Maker gets ALT + let settlement_asset_input_index: u32 = 0; + + let (burn_grantor_settlement_output_index, burn_grantor_collateral_output_index, settlement_output_index): (u32, u32, u32) = match is_change_needed { + true => (1, 2, 3), + false => (0, 1, 2), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(settlement_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset amount is correct + divmod_eq(amount_to_get, param::GRANTOR_PER_SETTLEMENT_ASSET, grantor_amount_to_burn); + + // Burn grantor settlement and collateral tokens + ensure_output_is_op_return(burn_grantor_settlement_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_settlement_output_index, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_amount_to_burn); + ensure_output_is_op_return(burn_grantor_collateral_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_collateral_output_index, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_amount_to_burn); + + // Ensure settlement asset transferred to user + ensure_correct_return_at(settlement_output_index, param::SETTLEMENT_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + false => { + // Maker gets the LBTC + let collateral_input_index: u32 = 0; + + let (burn_grantor_collateral_output_index, burn_grantor_settlement_output_index, collateral_output_index): (u32, u32, u32) = match is_change_needed { + true => (1, 2, 3), + false => (0, 1, 2), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(amount_to_get, param::GRANTOR_PER_SETTLEMENT_COLLATERAL, grantor_amount_to_burn); + + // Burn grantor collateral and settlement tokens + ensure_output_is_op_return(burn_grantor_collateral_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_collateral_output_index, param::GRANTOR_COLLATERAL_TOKEN_ASSET, grantor_amount_to_burn); + ensure_output_is_op_return(burn_grantor_settlement_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_settlement_output_index, param::GRANTOR_SETTLEMENT_TOKEN_ASSET, grantor_amount_to_burn); + + // Ensure collateral transferred to user + ensure_correct_return_at(collateral_output_index, param::COLLATERAL_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + } +} + +fn taker_settlement_path(price_at_current_block_height: u64, oracle_sig: Signature, filler_amount_to_burn: u64, amount_to_get: u64, is_change_needed: bool) { + jet::check_lock_height(param::SETTLEMENT_HEIGHT); + checksig_priceblock(param::ORACLE_PK, param::SETTLEMENT_HEIGHT, price_at_current_block_height, oracle_sig); + + match jet::le_64(price_at_current_block_height, param::STRIKE_PRICE) { + true => { + // Taker receives LBTC principal+interest + let collateral_input_index: u32 = 0; + + let (burn_filler_output_index, collateral_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(amount_to_get, param::FILLER_PER_SETTLEMENT_COLLATERAL, filler_amount_to_burn); + + // Burn filler token + ensure_output_is_op_return(burn_filler_output_index); + ensure_output_asset_with_amount_eq(burn_filler_output_index, param::FILLER_TOKEN_ASSET, filler_amount_to_burn); + + // Ensure collateral transferred to user + ensure_correct_return_at(collateral_output_index, param::COLLATERAL_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + false => { + // Taker receives ALT + let settlement_asset_input_index: u32 = 0; + + let (burn_filler_output_index, settlement_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(settlement_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset amount is correct + divmod_eq(amount_to_get, param::FILLER_PER_SETTLEMENT_ASSET, filler_amount_to_burn); + + // Burn filler token + ensure_output_is_op_return(burn_filler_output_index); + ensure_output_asset_with_amount_eq(burn_filler_output_index, param::FILLER_TOKEN_ASSET, filler_amount_to_burn); + + // Ensure filler token transferred to user + ensure_correct_return_at(settlement_output_index, param::SETTLEMENT_ASSET_ID, amount_to_get, param::FEE_BASIS_POINTS); + }, + } +} + +fn main() { + let token_branch: Either<(), ()> = witness::TOKEN_BRANCH; + let merge_branch: Either, ()> = witness::MERGE_BRANCH; + + match witness::PATH { + Left(funding_or_settlement: Either, (u64, Signature, u64, u64, bool)>) => match funding_or_settlement { + // Funding branches + Left(funding_params: Either<(u64, u64, u64, u64), (u64, u64, bool)>) => match funding_params { + // Maker funding: (principal_collateral_amount, principal_asset_amount, interest_collateral_amount, interest_asset_amount) + Left(params: (u64, u64, u64, u64)) => { + let (principal_collateral_amount, principal_asset_amount, interest_collateral_amount, interest_asset_amount): (u64, u64, u64, u64) = params; + maker_funding_path(principal_collateral_amount, principal_asset_amount, interest_collateral_amount, interest_asset_amount) + }, + // Taker funding: (collateral_amount_to_deposit, filler_token_amount_to_get, is_change_needed) + Right(params: (u64, u64, bool)) => { + let (collateral_amount_to_deposit, filler_token_amount_to_get, is_change_needed): (u64, u64, bool) = params; + taker_funding_path(collateral_amount_to_deposit, filler_token_amount_to_get, is_change_needed) + }, + }, + // Settlement branches (oracle price attested) + Right(params: (u64, Signature, u64, u64, bool)) => { + let (price_at_current_block_height, oracle_sig, amount_to_burn, amount_to_get, is_change_needed): (u64, Signature, u64, u64, bool) = params; + + match token_branch { + // Maker settlement: burn grantor token + Left(u: ()) => maker_settlement_path(price_at_current_block_height, oracle_sig, amount_to_burn, amount_to_get, is_change_needed), + // Taker settlement: burn filler token + Right(u: ()) => taker_settlement_path(price_at_current_block_height, oracle_sig, amount_to_burn, amount_to_get, is_change_needed), + } + }, + }, + // Termination flows (early termination or post-expiry) or Merge flows + Right(termination_or_maker_or_merge: Either, ()>) => match termination_or_maker_or_merge { + Left(termination_or_maker: Either<(bool, u64, u64), (bool, u64, u64)>) => match termination_or_maker { + // Taker early termination: (is_change_needed, filler_token_amount_to_return, collateral_amount_to_get) + Left(params: (bool, u64, u64)) => { + let (is_change_needed, filler_token_amount_to_return, collateral_amount_to_get): (bool, u64, u64) = params; + taker_early_termination_path(filler_token_amount_to_return, collateral_amount_to_get, is_change_needed) + }, + // Maker termination (burn grantor token): choose collateral vs settlement token via token_branch + Right(params: (bool, u64, u64)) => { + let (is_change_needed, grantor_token_amount_to_burn, amount_to_get): (bool, u64, u64) = params; + + match token_branch { + // Burn grantor collateral token -> receive collateral + Left(u: ()) => maker_collateral_termination_path(grantor_token_amount_to_burn, amount_to_get, is_change_needed), + // Burn grantor settlement token -> receive settlement asset + Right(u: ()) => maker_settlement_termination_path(grantor_token_amount_to_burn, amount_to_get, is_change_needed), + } + }, + }, + Right(u: ()) => { + // Merge tokens based on MERGE_BRANCH discriminator + match merge_branch { + Left(left_or_right: Either<(), ()>) => match left_or_right { + Left(u: ()) => merge_2_tokens(), + Right(u: ()) => merge_3_tokens(), + }, + Right(u: ()) => merge_4_tokens(), + } + }, + }, + } + +} diff --git a/examples/basic/simf/another_dir/array_tr_storage.simf b/examples/basic/simf/another_dir/array_tr_storage.simf new file mode 100644 index 0000000..4918cf3 --- /dev/null +++ b/examples/basic/simf/another_dir/array_tr_storage.simf @@ -0,0 +1,81 @@ +/* + * Extends `bytes32_tr_storage` using `array_fold` for larger buffers. + * Optimized for small, fixed-size states where linear hashing is more efficient + * than Merkle Trees. By avoiding proof overhead like sibling hashes, we reduce + * witness size and simplify contract logic for small N. + * This approach is particularly advantageous when updating all slots within every transaction. + */ + +fn hash_array_tr_storage(elem: u256, ctx: Ctx8) -> Ctx8 { + jet::sha_256_ctx_8_add_32(ctx, elem) +} + +fn hash_array_tr_storage_with_update(elem: u256, triplet: (Ctx8, u16, u16)) -> (Ctx8, u16, u16) { + let (ctx, i, changed_index): (Ctx8, u16, u16) = triplet; + + match jet::eq_16(i, changed_index) { + true => { + let (_, val): (bool, u16) = jet::increment_16(i); + + // There may be arbitrary logic here + let (state1, state2, state3, state4): (u64, u64, u64, u64) = ::into(elem); + let new_state4: u64 = 20; + + let new_state: u256 = <(u64, u64, u64, u64)>::into((state1, state2, state3, new_state4)); + ( + jet::sha_256_ctx_8_add_32(ctx, new_state), + val, + changed_index, + ) + }, + false => { + let (_, val): (bool, u16) = jet::increment_16(i); + ( + jet::sha_256_ctx_8_add_32(ctx, elem), + val, + changed_index, + ) + } + } +} + +fn script_hash_for_input_script(state: [u256; 3], changed_index: Option) -> u256 { + let tap_leaf: u256 = jet::tapleaf_hash(); + let ctx: Ctx8 = jet::tapdata_init(); + + let (ctx, _, _): (Ctx8, u16, u16) = match changed_index { + Some(ind: u16) => { + array_fold::(state, (ctx, 0, ind)) + }, + None => { + (array_fold::(state, ctx), 0, 0) + } + }; + + let computed: u256 = jet::sha_256_ctx_8_finalize(ctx); + let tap_node: u256 = jet::build_tapbranch(tap_leaf, computed); + + let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; + let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node); + + let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init(); + let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32 + let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key); + jet::sha_256_ctx_8_finalize(hash_ctx3) +} + +fn main() { + let state: [u256; 3] = witness::STATE; + + // Assert that the input is correct, i.e. "load". + assert!(jet::eq_256( + script_hash_for_input_script(state, None), + unwrap(jet::input_script_hash(jet::current_index())) + )); + + // Assert that the output is correct, i.e. "store". + assert!(jet::eq_256( + script_hash_for_input_script(state, Some(witness::CHANGED_INDEX)), + unwrap(jet::output_script_hash(jet::current_index())) + )); +} \ No newline at end of file diff --git a/examples/basic/simf/module/option_offer.simf b/examples/basic/simf/module/option_offer.simf new file mode 100644 index 0000000..5cb2108 --- /dev/null +++ b/examples/basic/simf/module/option_offer.simf @@ -0,0 +1,213 @@ +/* + * Option Offer + * + * A covenant that allows a user to deposit collateral and premium assets, + * and have a counterparty swap settlement asset for both. + * The user can withdraw accumulated settlement asset at any time (with signature). + * After expiry, the user can reclaim any remaining collateral and premium (with signature). + * + * Paths: + * 1. Exercise: Counterparty swaps settlement asset for collateral + premium (no time restriction, optional change) + * 2. Withdraw: User withdraws settlement asset (no time restriction, signature required, full amount) + * 3. Expiry: User reclaims collateral + premium (after expiry, signature required, full amount) + * + * Constraints: + * settlement_amount = COLLATERAL_PER_CONTRACT * collateral_amount + * premium_amount = PREMIUM_PER_COLLATERAL * collateral_amount + */ + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +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_zero_bit(bit: bool) { + assert!(jet::eq_1(::into(bit), 0)); +} + +fn ensure_output_asset_with_amount_eq(index: u32, expected_bits: u256, expected_amount: u64) { + let (asset, amount): (u256, u64) = get_output_explicit_asset_amount(index); + assert!(jet::eq_256(asset, expected_bits)); + assert!(jet::eq_64(amount, expected_amount)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +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 check_user_signature(sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((param::USER_PUBKEY, msg), sig); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, asset_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, asset_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +/* + * Exercise Path + * + * Counterparty swaps settlement asset for collateral + premium. + * No time restriction - works before and after expiry. + * + * Constraints: + * settlement_amount = COLLATERAL_PER_CONTRACT * collateral_amount + * premium_amount = PREMIUM_PER_COLLATERAL * collateral_amount + * + * Layout: + * + * Both: + * Input[0]: Collateral from covenant + * Input[1]: Premium from covenant + * + * With change (partial swap): + * Output[0]: Collateral change → covenant + * Output[1]: Premium change → covenant + * Output[2]: Settlement asset → covenant + * Output[3]: Collateral → counterparty + * Output[4]: Premium → counterparty + * + * Without change (full swap): + * Output[0]: Settlement asset → covenant + * Output[1]: Collateral → counterparty + * Output[2]: Premium → counterparty + */ +fn exercise_path(collateral_amount: u64, is_change_needed: bool) { + assert!(jet::le_32(jet::current_index(), 1)); + + let expected_covenant_script_hash: u256 = get_input_script_hash(0); + + assert!(jet::eq_256(get_input_script_hash(1), expected_covenant_script_hash)); + + let premium_amount_u128: u128 = jet::multiply_64(collateral_amount, param::PREMIUM_PER_COLLATERAL); + let (left_part, premium_amount): (u64, u64) = dbg!(::into(premium_amount_u128)); + assert!(jet::eq_64(left_part, 0)); + + // Check collateral changes + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount, expected_covenant_script_hash, is_change_needed); + ensure_correct_change_at_index(1, param::PREMIUM_ASSET_ID, premium_amount, expected_covenant_script_hash, is_change_needed); + + let (settlement_output_index, collateral_output_index, premium_output_index): (u32, u32, u32) = match is_change_needed { + true => (2, 3, 4), + false => (0, 1, 2), + }; + + ensure_output_script_hash_eq(settlement_output_index, expected_covenant_script_hash); + + let (output_asset, settlement_amount): (u256, u64) = get_output_explicit_asset_amount(settlement_output_index); + assert!(jet::eq_256(output_asset, param::SETTLEMENT_ASSET_ID)); + + divmod_eq(settlement_amount, param::COLLATERAL_PER_CONTRACT, collateral_amount); + + ensure_output_asset_with_amount_eq(collateral_output_index, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(premium_output_index, param::PREMIUM_ASSET_ID, premium_amount); +} + +/* + * Withdraw Path + * + * User withdraws accumulated settlement asset. + * No time restriction. + * Requires signature from USER_PUBKEY. + * No change - full withdrawal only. + * + * Layout: + * Input[0]: Settlement asset from covenant + * Output[0]: Settlement asset → user (any address) + */ +fn withdraw_path(sig: Signature) { + assert!(jet::eq_32(jet::current_index(), 0)); + + let (input_asset, input_amount): (u256, u64) = get_input_explicit_asset_amount(0); + assert!(jet::eq_256(input_asset, param::SETTLEMENT_ASSET_ID)); + + check_user_signature(sig); + + ensure_output_asset_with_amount_eq(0, param::SETTLEMENT_ASSET_ID, input_amount); +} + +/* + * Expiry Path + * + * User reclaims remaining collateral and premium after expiry. + * Only allowed after EXPIRY_TIME. + * Requires signature from USER_PUBKEY. + * No change - full reclaim only. + * + * Layout: + * Input[0]: Collateral from covenant + * Input[1]: Premium from covenant + * Output[0]: Collateral → user (any address) + * Output[1]: Premium → user (any address) + */ +fn expiry_path(sig: Signature) { + jet::check_lock_time(param::EXPIRY_TIME); + + assert!(jet::le_32(jet::current_index(), 1)); + + let expected_covenant_script_hash: u256 = get_input_script_hash(0); + + assert!(jet::eq_256(get_input_script_hash(1), expected_covenant_script_hash)); + + let (collateral_asset, collateral_amount): (u256, u64) = get_input_explicit_asset_amount(0); + assert!(jet::eq_256(collateral_asset, param::COLLATERAL_ASSET_ID)); + + let (premium_asset, premium_amount): (u256, u64) = get_input_explicit_asset_amount(1); + assert!(jet::eq_256(premium_asset, param::PREMIUM_ASSET_ID)); + + check_user_signature(sig); + + ensure_output_asset_with_amount_eq(0, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(1, param::PREMIUM_ASSET_ID, premium_amount); +} + +fn main() { + match witness::PATH { + Left(params: (u64, bool)) => { + let (collateral_amount, is_change_needed): (u64, bool) = params; + exercise_path(collateral_amount, is_change_needed) + }, + Right(withdraw_or_expiry: Either) => match withdraw_or_expiry { + Left(sig: Signature) => withdraw_path(sig), + Right(sig: Signature) => expiry_path(sig), + }, + } +} diff --git a/examples/basic/simf/options.simf b/examples/basic/simf/options.simf new file mode 100644 index 0000000..e7da014 --- /dev/null +++ b/examples/basic/simf/options.simf @@ -0,0 +1,395 @@ +/* + * Options + * + * Important: Currently only the LBTC collateral is supported. + * + * Based on the https://blockstream.com/assets/downloads/pdf/options-whitepaper.pdf + * + * This contract implements cash-settled European-style options using covenant-locked collateral. + * + * Room for optimization: + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/2 (Use input asset to determine option covenent type) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/3 (Simplify match token_branch in funding_path.) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/4 (why batching is hard to implement) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/5 (Reduce Contract Parameters) + * - https://github.com/BlockstreamResearch/simplicity-contracts/issues/21 (explains why funding is limited) + */ + +/// Assert: a == b * expected_q, via divmod +fn divmod_eq(a: u64, b: u64, expected_q: u64) { + let (q, r): (u64, u64) = jet::div_mod_64(a, b); + assert!(jet::eq_64(q, expected_q)); + assert!(jet::eq_64(r, 0)); +} + +fn get_output_script_hash(index: u32) -> u256 { + unwrap(jet::output_script_hash(index)) +} + +fn get_input_script_hash(index: u32) -> u256 { + unwrap(jet::input_script_hash(index)) +} + +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_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) = jet::add_32(index, amount); + ensure_zero_bit(carry); + result +} + +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 ensure_input_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::input_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +fn ensure_output_asset_eq(index: u32, expected_bits: u256) { + let asset: Asset1 = unwrap(jet::output_asset(index)); + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + assert!(jet::eq_256(asset_bits, expected_bits)); +} + +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_input_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), expected)); +} + +fn ensure_output_script_hash_eq(index: u32, expected: u256) { + assert!(jet::eq_256(unwrap(jet::output_script_hash(index)), expected)); +} + +fn ensure_correct_change_at_index(index: u32, asset_id: u256, asset_amount_to_spend: u64, contract_script_hash: u256, is_change_needed: bool) { + let (asset_bits, available_asset_amount): (u256, u64) = get_input_explicit_asset_amount(index); + assert!(jet::eq_256(unwrap(jet::input_script_hash(index)), contract_script_hash)); + assert!(jet::eq_32(jet::current_index(), index)); + + match is_change_needed { + true => { + ensure_input_and_output_script_hash_eq(index); + + let (carry, collateral_change): (bool, u64) = jet::subtract_64(available_asset_amount, asset_amount_to_spend); + ensure_zero_bit(carry); + ensure_output_asset_with_amount_eq(index, asset_id, collateral_change); + }, + false => assert!(jet::eq_64(asset_amount_to_spend, available_asset_amount)), + } +} + +fn check_y(expected_y: Fe, actual_y: Fe) { + match jet::eq_256(expected_y, actual_y) { + true => {}, + false => { + assert!(jet::eq_256(expected_y, jet::fe_negate(actual_y))); + } + }; +} + +fn ensure_input_and_output_reissuance_token_eq(index: u32) { + let (input_asset, input_amount): (Asset1, Amount1) = unwrap(jet::input_amount(index)); + let (output_asset, output_amount): (Asset1, Amount1) = unwrap(jet::output_amount(index)); + + match (input_asset) { + Left(in_conf: Point) => { + let (input_asset_parity, input_asset_x): (u1, u256) = in_conf; + let (output_asset_parity, output_asset_x): (u1, u256) = unwrap_left::(output_asset); + + assert!(jet::eq_1(input_asset_parity, output_asset_parity)); + assert!(jet::eq_256(input_asset_x, output_asset_x)); + }, + Right(in_expl: u256) => { + let out_expl: u256 = unwrap_right::(output_asset); + assert!(jet::eq_256(in_expl, out_expl)); + } + }; + + match (input_amount) { + Left(in_conf: Point) => { + let (input_amount_parity, input_amount_x): (u1, u256) = in_conf; + let (output_amount_parity, output_amount_x): (u1, u256) = unwrap_left::(output_amount); + + assert!(jet::eq_1(input_amount_parity, output_amount_parity)); + assert!(jet::eq_256(input_amount_x, output_amount_x)); + }, + Right(in_expl: u64) => { + let out_expl: u64 = unwrap_right::(output_amount); + assert!(jet::eq_64(in_expl, out_expl)); + } + }; +} + +// Verify that a reissuance token commitment matches the expected token ID using provided blinding factors. +// Reissuance tokens are confidential because, in Elements, +// the asset must be provided in blinded form in order to reissue tokens. +// https://github.com/BlockstreamResearch/simplicity-contracts/issues/21#issuecomment-3691599583 +fn verify_token_commitment(actual_asset: Asset1, actual_amount: Amount1, expected_token_id: u256, abf: u256, vbf: u256) { + match actual_asset { + Left(conf_token: Point) => { + let amount_scalar: u256 = 1; + let (actual_ax, actual_ay): Ge = unwrap(jet::decompress(conf_token)); + + let gej_point: Gej = (jet::hash_to_curve(expected_token_id), 1); + let asset_blind_point: Gej = jet::generate(abf); + + let asset_generator: Gej = jet::gej_add(gej_point, asset_blind_point); + let (ax, ay): Ge = unwrap(jet::gej_normalize(asset_generator)); + + assert!(jet::eq_256(actual_ax, ax)); + check_y(actual_ay, ay); + + // Check amount + let conf_val: Point = unwrap_left::(actual_amount); + let (actual_vx, actual_vy): Ge = unwrap(jet::decompress(conf_val)); + + let amount_part: Gej = jet::scale(amount_scalar, asset_generator); + let vbf_part: Gej = jet::generate(vbf); + + let value_generator: Gej = jet::gej_add(amount_part, vbf_part); + let (vx, vy): Ge = unwrap(jet::gej_normalize(value_generator)); + + assert!(jet::eq_256(actual_vx, vx)); + check_y(actual_vy, vy); + }, + Right(reissuance_token: u256) => { + let expected_amount: u64 = 1; + let actual_amount: u64 = unwrap_right::(actual_amount); + + assert!(jet::eq_64(expected_amount, actual_amount)); + assert!(jet::eq_256(reissuance_token, expected_token_id)); + } + }; +} + +fn verify_output_reissuance_token(index: u32, expected_token_id: u256, abf: u256, vbf: u256) { + let (asset, amount): (Asset1, Amount1) = unwrap(jet::output_amount(index)); + verify_token_commitment(asset, amount, expected_token_id, abf, vbf); +} + +fn verify_input_reissuance_token(index: u32, expected_token_id: u256, abf: u256, vbf: u256) { + let (asset, amount): (Asset1, Amount1) = unwrap(jet::input_amount(index)); + verify_token_commitment(asset, amount, expected_token_id, abf, vbf); +} + +/* + * Funding Path + */ +fn funding_path( + expected_asset_amount: u64, + input_option_abf: u256, + input_option_vbf: u256, + input_grantor_abf: u256, + input_grantor_vbf: u256, + output_option_abf: u256, + output_option_vbf: u256, + output_grantor_abf: u256, + output_grantor_vbf: u256 +) { + ensure_input_and_output_script_hash_eq(0); + ensure_input_and_output_script_hash_eq(1); + + verify_input_reissuance_token(0, param::OPTION_REISSUANCE_TOKEN_ASSET, input_option_abf, input_option_vbf); + verify_input_reissuance_token(1, param::GRANTOR_REISSUANCE_TOKEN_ASSET, input_grantor_abf, input_grantor_vbf); + + verify_output_reissuance_token(0, param::OPTION_REISSUANCE_TOKEN_ASSET, output_option_abf, output_option_vbf); + verify_output_reissuance_token(1, param::GRANTOR_REISSUANCE_TOKEN_ASSET, output_grantor_abf, output_grantor_vbf); + + assert!(dbg!(jet::eq_256(get_output_script_hash(0), get_output_script_hash(1)))); + + assert!(jet::le_32(jet::current_index(), 1)); + + ensure_output_script_hash_eq(2, get_output_script_hash(0)); + + let (collateral_asset_bits, collateral_amount): (u256, u64) = get_output_explicit_asset_amount(2); + let option_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(0)))); + let grantor_token_amount: u64 = unwrap_right::<(u1, u256)>(unwrap(unwrap(jet::issuance_asset_amount(1)))); + assert!(jet::eq_64(option_token_amount, grantor_token_amount)); + + divmod_eq(collateral_amount, param::COLLATERAL_PER_CONTRACT, option_token_amount); + divmod_eq(expected_asset_amount, param::SETTLEMENT_PER_CONTRACT, option_token_amount); + + ensure_output_asset_with_amount_eq(2, param::COLLATERAL_ASSET_ID, collateral_amount); + ensure_output_asset_with_amount_eq(3, param::OPTION_TOKEN_ASSET, option_token_amount); + ensure_output_asset_with_amount_eq(4, param::GRANTOR_TOKEN_ASSET, grantor_token_amount); +} + +/* + * Cancellation Path + */ +fn cancellation_path(amount_to_burn: u64, collateral_amount_to_withdraw: u64, is_change_needed: bool) { + let collateral_input_index: u32 = 0; + let option_input_index: u32 = 1; + let grantor_input_index: u32 = 2; + + let (burn_option_output_index, burn_grantor_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_withdraw, expected_current_script_hash, is_change_needed); + + // Burn option and grantor tokens + ensure_output_is_op_return(burn_option_output_index); + ensure_output_is_op_return(burn_grantor_output_index); + + ensure_output_asset_with_amount_eq(burn_option_output_index, param::OPTION_TOKEN_ASSET, amount_to_burn); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, amount_to_burn); + + // Ensure returned collateral amount is correct + divmod_eq(collateral_amount_to_withdraw, param::COLLATERAL_PER_CONTRACT, amount_to_burn); +} + +/* + * Exercise Path + */ +fn exercise_path(option_amount_to_burn: u64, collateral_amount_to_get: u64, asset_amount_to_pay: u64, is_change_needed: bool) { + jet::check_lock_time(param::START_TIME); + + let collateral_input_index: u32 = 0; + + let (burn_option_output_index, asset_to_covenant_output_index): (u32, u32) = match is_change_needed { + true => (1, 2), + false => (0, 1), + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount_to_get, expected_current_script_hash, is_change_needed); + + // Ensure collateral and asset amounts are correct + divmod_eq(collateral_amount_to_get, param::COLLATERAL_PER_CONTRACT, option_amount_to_burn); + divmod_eq(asset_amount_to_pay, param::SETTLEMENT_PER_CONTRACT, option_amount_to_burn); + + // Burn option token + ensure_output_is_op_return(burn_option_output_index); + ensure_output_asset_with_amount_eq(burn_option_output_index, param::OPTION_TOKEN_ASSET, option_amount_to_burn); + + // Ensure settlement asset and script hash are correct + ensure_output_asset_with_amount_eq(asset_to_covenant_output_index, param::SETTLEMENT_ASSET_ID, asset_amount_to_pay); + ensure_output_script_hash_eq(asset_to_covenant_output_index, expected_current_script_hash); +} + +/* + * Settlement Path + */ +fn settlement_path(grantor_token_amount_to_burn: u64, asset_amount: u64, is_change_needed: bool) { + jet::check_lock_time(param::START_TIME); + + let target_asset_input_index: u32 = 0; + + let burn_grantor_output_index: u32 = match is_change_needed { + true => 1, + false => 0, + }; + + let expected_current_script_hash: u256 = get_input_script_hash(target_asset_input_index); + + // Check and ensure settlement asset change + ensure_correct_change_at_index(0, param::SETTLEMENT_ASSET_ID, asset_amount, expected_current_script_hash, is_change_needed); + + // Ensure settlement asset and grantor token amounts are correct + divmod_eq(asset_amount, param::SETTLEMENT_PER_CONTRACT, grantor_token_amount_to_burn); + + // Burn grantor token + ensure_output_is_op_return(burn_grantor_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, grantor_token_amount_to_burn); +} + +/* + * Expiry Path + */ +fn expiry_path(grantor_token_amount_to_burn: u64, collateral_amount: u64, is_change_needed: bool) { + jet::check_lock_time(param::EXPIRY_TIME); + + let collateral_input_index: u32 = 0; + + let burn_grantor_output_index: u32 = match is_change_needed { + true => 1, + false => 0, + }; + + let expected_current_script_hash: u256 = get_input_script_hash(collateral_input_index); + + // Check and ensure collateral change + ensure_correct_change_at_index(0, param::COLLATERAL_ASSET_ID, collateral_amount, expected_current_script_hash, is_change_needed); + + // Ensure collateral amount is correct + divmod_eq(collateral_amount, param::COLLATERAL_PER_CONTRACT, grantor_token_amount_to_burn); + + // Burn grantor token + ensure_output_is_op_return(burn_grantor_output_index); + ensure_output_asset_with_amount_eq(burn_grantor_output_index, param::GRANTOR_TOKEN_ASSET, grantor_token_amount_to_burn); +} + +fn main() { + match witness::PATH { + Left(left_or_right: Either<(u64, u256, u256, u256, u256, u256, u256, u256, u256), Either<(bool, u64, u64, u64), (bool, u64, u64)>>) => match left_or_right { + Left(params: (u64, u256, u256, u256, u256, u256, u256, u256, u256)) => { + let (expected_asset_amount, input_option_abf, input_option_vbf, input_grantor_abf, input_grantor_vbf, output_option_abf, output_option_vbf, output_grantor_abf, output_grantor_vbf): (u64, u256, u256, u256, u256, u256, u256, u256, u256) = params; + funding_path( + expected_asset_amount, + input_option_abf, input_option_vbf, + input_grantor_abf, input_grantor_vbf, + output_option_abf, output_option_vbf, + output_grantor_abf, output_grantor_vbf + ); + }, + Right(exercise_or_settlement: Either<(bool, u64, u64, u64), (bool, u64, u64)>) => match exercise_or_settlement { + Left(params: (bool, u64, u64, u64)) => { + let (is_change_needed, amount_to_burn, collateral_amount, asset_amount): (bool, u64, u64, u64) = dbg!(params); + exercise_path(amount_to_burn, collateral_amount, asset_amount, is_change_needed) + }, + Right(params: (bool, u64, u64)) => { + let (is_change_needed, amount_to_burn, asset_amount): (bool, u64, u64) = dbg!(params); + settlement_path(amount_to_burn, asset_amount, is_change_needed) + }, + }, + }, + Right(left_or_right: Either<(bool, u64, u64), (bool, u64, u64)>) => match left_or_right { + Left(params: (bool, u64, u64)) => { + let (is_change_needed, grantor_token_amount_to_burn, collateral_amount): (bool, u64, u64) = params; + expiry_path(grantor_token_amount_to_burn, collateral_amount, is_change_needed) + }, + Right(params: (bool, u64, u64)) => { + let (is_change_needed, amount_to_burn, collateral_amount): (bool, u64, u64) = params; + cancellation_path(amount_to_burn, collateral_amount, is_change_needed) + }, + }, + } +} diff --git a/simplex/crates/sdk/src/presets/simf/p2pk.simf b/examples/basic/simf/p2pk.simf similarity index 100% rename from simplex/crates/sdk/src/presets/simf/p2pk.simf rename to examples/basic/simf/p2pk.simf diff --git a/examples/basic/src/artifacts/another_dir/another_module/bytes32_tr_storage.rs b/examples/basic/src/artifacts/another_dir/another_module/bytes32_tr_storage.rs new file mode 100644 index 0000000..07978a1 --- /dev/null +++ b/examples/basic/src/artifacts/another_dir/another_module/bytes32_tr_storage.rs @@ -0,0 +1,24 @@ +use simplex::simplex_macros::include_simf; +use simplex::simplex_sdk::program::{ArgumentsTrait, Program}; +use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +pub struct Bytes32TrStorageProgram { + program: Program, +} +impl Bytes32TrStorageProgram { + pub const SOURCE: &'static str = derived_bytes32_tr_storage::BYTES32_TR_STORAGE_CONTRACT_SOURCE; + pub fn new( + public_key: XOnlyPublicKey, + arguments: impl ArgumentsTrait + 'static, + ) -> Self { + Self { + program: Program::new(Self::SOURCE, public_key, Box::new(arguments)), + } + } + pub fn get_program(&self) -> &Program { + &self.program + } + pub fn get_program_mut(&mut self) -> &mut Program { + &mut self.program + } +} +include_simf!("simf/another_dir/another_module/bytes32_tr_storage.simf"); diff --git a/examples/basic/src/artifacts/another_dir/another_module/dual_currency_deposit.rs b/examples/basic/src/artifacts/another_dir/another_module/dual_currency_deposit.rs new file mode 100644 index 0000000..1cdc1ce --- /dev/null +++ b/examples/basic/src/artifacts/another_dir/another_module/dual_currency_deposit.rs @@ -0,0 +1,24 @@ +use simplex::simplex_macros::include_simf; +use simplex::simplex_sdk::program::{ArgumentsTrait, Program}; +use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +pub struct DualCurrencyDepositProgram { + program: Program, +} +impl DualCurrencyDepositProgram { + pub const SOURCE: &'static str = derived_dual_currency_deposit::DUAL_CURRENCY_DEPOSIT_CONTRACT_SOURCE; + pub fn new( + public_key: XOnlyPublicKey, + arguments: impl ArgumentsTrait + 'static, + ) -> Self { + Self { + program: Program::new(Self::SOURCE, public_key, Box::new(arguments)), + } + } + pub fn get_program(&self) -> &Program { + &self.program + } + pub fn get_program_mut(&mut self) -> &mut Program { + &mut self.program + } +} +include_simf!("simf/another_dir/another_module/dual_currency_deposit.simf"); diff --git a/examples/basic/src/artifacts/another_dir/another_module/mod.rs b/examples/basic/src/artifacts/another_dir/another_module/mod.rs new file mode 100644 index 0000000..121de82 --- /dev/null +++ b/examples/basic/src/artifacts/another_dir/another_module/mod.rs @@ -0,0 +1,2 @@ +pub mod dual_currency_deposit; +pub mod bytes32_tr_storage; diff --git a/examples/basic/src/artifacts/another_dir/array_tr_storage.rs b/examples/basic/src/artifacts/another_dir/array_tr_storage.rs new file mode 100644 index 0000000..6d5f8f0 --- /dev/null +++ b/examples/basic/src/artifacts/another_dir/array_tr_storage.rs @@ -0,0 +1,24 @@ +use simplex::simplex_macros::include_simf; +use simplex::simplex_sdk::program::{ArgumentsTrait, Program}; +use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +pub struct ArrayTrStorageProgram { + program: Program, +} +impl ArrayTrStorageProgram { + pub const SOURCE: &'static str = derived_array_tr_storage::ARRAY_TR_STORAGE_CONTRACT_SOURCE; + pub fn new( + public_key: XOnlyPublicKey, + arguments: impl ArgumentsTrait + 'static, + ) -> Self { + Self { + program: Program::new(Self::SOURCE, public_key, Box::new(arguments)), + } + } + pub fn get_program(&self) -> &Program { + &self.program + } + pub fn get_program_mut(&mut self) -> &mut Program { + &mut self.program + } +} +include_simf!("simf/another_dir/array_tr_storage.simf"); diff --git a/examples/basic/src/artifacts/another_dir/mod.rs b/examples/basic/src/artifacts/another_dir/mod.rs new file mode 100644 index 0000000..f7bff3b --- /dev/null +++ b/examples/basic/src/artifacts/another_dir/mod.rs @@ -0,0 +1,2 @@ +pub mod array_tr_storage; +pub mod another_module; diff --git a/examples/basic/src/artifacts/mod.rs b/examples/basic/src/artifacts/mod.rs new file mode 100644 index 0000000..b78b75d --- /dev/null +++ b/examples/basic/src/artifacts/mod.rs @@ -0,0 +1,4 @@ +pub mod p2pk; +pub mod options; +pub mod module; +pub mod another_dir; diff --git a/examples/basic/src/artifacts/module/mod.rs b/examples/basic/src/artifacts/module/mod.rs new file mode 100644 index 0000000..920b31d --- /dev/null +++ b/examples/basic/src/artifacts/module/mod.rs @@ -0,0 +1 @@ +pub mod option_offer; diff --git a/examples/basic/src/artifacts/module/option_offer.rs b/examples/basic/src/artifacts/module/option_offer.rs new file mode 100644 index 0000000..17e639e --- /dev/null +++ b/examples/basic/src/artifacts/module/option_offer.rs @@ -0,0 +1,24 @@ +use simplex::simplex_macros::include_simf; +use simplex::simplex_sdk::program::{ArgumentsTrait, Program}; +use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; +pub struct OptionOfferProgram { + program: Program, +} +impl OptionOfferProgram { + pub const SOURCE: &'static str = derived_option_offer::OPTION_OFFER_CONTRACT_SOURCE; + pub fn new( + public_key: XOnlyPublicKey, + arguments: impl ArgumentsTrait + 'static, + ) -> Self { + Self { + program: Program::new(Self::SOURCE, public_key, Box::new(arguments)), + } + } + pub fn get_program(&self) -> &Program { + &self.program + } + pub fn get_program_mut(&mut self) -> &mut Program { + &mut self.program + } +} +include_simf!("simf/module/option_offer.simf"); diff --git a/example/src/artifacts/options.rs b/examples/basic/src/artifacts/options.rs similarity index 100% rename from example/src/artifacts/options.rs rename to examples/basic/src/artifacts/options.rs diff --git a/example/src/artifacts/p2pk.rs b/examples/basic/src/artifacts/p2pk.rs similarity index 100% rename from example/src/artifacts/p2pk.rs rename to examples/basic/src/artifacts/p2pk.rs diff --git a/example/src/main.rs b/examples/basic/src/main.rs similarity index 82% rename from example/src/main.rs rename to examples/basic/src/main.rs index ea4d1bb..7c29088 100644 --- a/example/src/main.rs +++ b/examples/basic/src/main.rs @@ -30,7 +30,7 @@ fn get_p2pk(signer: &Signer) -> (P2pkProgram, Script) { } fn spend_p2wpkh(signer: &Signer, provider: &EsploraProvider) -> Txid { - let (_, p2pk_script) = get_p2pk(&signer); + let (_, p2pk_script) = get_p2pk(signer); let mut ft = FinalTransaction::new(SimplicityNetwork::LiquidTestnet); @@ -49,13 +49,11 @@ fn spend_p2wpkh(signer: &Signer, provider: &EsploraProvider) -> Txid { } fn spend_p2pk(signer: &Signer, provider: &EsploraProvider) -> Txid { - let (p2pk, p2pk_script) = get_p2pk(&signer); + let (p2pk, p2pk_script) = get_p2pk(signer); let mut p2pk_utxos = provider.fetch_scripthash_utxos(&p2pk_script).unwrap(); - p2pk_utxos.retain(|el| { - el.1.asset.explicit().unwrap() == SimplicityNetwork::LiquidTestnet.policy_asset() - }); + p2pk_utxos.retain(|el| el.1.asset.explicit().unwrap() == SimplicityNetwork::LiquidTestnet.policy_asset()); let mut ft = FinalTransaction::new(SimplicityNetwork::LiquidTestnet); @@ -64,11 +62,8 @@ fn spend_p2pk(signer: &Signer, provider: &EsploraProvider) -> Txid { }; ft.add_program_input( - PartialInput::new(p2pk_utxos[0].0.clone(), p2pk_utxos[0].1.clone()), - ProgramInput::new( - Box::new(p2pk.get_program().clone()), - Box::new(witness.clone()), - ), + PartialInput::new(p2pk_utxos[0].0, p2pk_utxos[0].1.clone()), + ProgramInput::new(Box::new(p2pk.get_program().clone()), Box::new(witness.clone())), RequiredSignature::Witness("SIGNATURE".to_string()), ) .unwrap(); @@ -81,26 +76,26 @@ fn spend_p2pk(signer: &Signer, provider: &EsploraProvider) -> Txid { res } -fn main() { +fn main() -> anyhow::Result<()> { let provider = EsploraProvider::new(ESPLORA_URL.to_string()); let signer = Signer::new( "exist carry drive collect lend cereal occur much tiger just involve mean", Box::new(provider.clone()), SimplicityNetwork::LiquidTestnet, - ) - .unwrap(); + )?; let tx = spend_p2wpkh(&signer, &provider); - provider.wait(&tx).unwrap(); + provider.wait(&tx)?; println!("Confirmed"); let tx = spend_p2pk(&signer, &provider); - provider.wait(&tx).unwrap(); + provider.wait(&tx)?; println!("Confirmed"); println!("OK"); + Ok(()) } diff --git a/examples/basic/tests/draft_test.rs b/examples/basic/tests/draft_test.rs new file mode 100644 index 0000000..e4fa66c --- /dev/null +++ b/examples/basic/tests/draft_test.rs @@ -0,0 +1,4 @@ +#[simplex::simplex_macros::test] +fn test_invocation_tx_tracking(_context: simplex::simplex_test::TestContext) -> anyhow::Result<()> { + Ok(()) +} diff --git a/simplex/rustfmt.toml b/rustfmt.toml similarity index 100% rename from simplex/rustfmt.toml rename to rustfmt.toml diff --git a/simplex/crates/cli/tests/hello/mod.rs b/simplex/crates/cli/tests/hello/mod.rs deleted file mode 100644 index 3a426b3..0000000 --- a/simplex/crates/cli/tests/hello/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod test333; diff --git a/simplex/crates/cli/tests/hello/test333.rs b/simplex/crates/cli/tests/hello/test333.rs deleted file mode 100644 index cda6b9b..0000000 --- a/simplex/crates/cli/tests/hello/test333.rs +++ /dev/null @@ -1,32 +0,0 @@ -use simplex_test::{TestContext, TestContextBuilder}; -use std::path::PathBuf; -use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt}; - -#[ignore] -#[test] -fn test_in_custom_folder_custom_333() -> anyhow::Result<()> { - fn test_in_custom_folder_custom_333(test_context: TestContext) -> anyhow::Result<()> { - assert_eq!(2 + 2, 4); - Ok(()) - }; - let test_context = match std::env::var("SIMPLEX_TEST_ENV") { - Err(e) => { - tracing::trace!( - "Test 'test_in_custom_folder_custom_333' connected with simplex is disabled, run `simplex test` in order to test it, err: '{e}'" - ); - panic!("Failed to run this test, required to use `simplex test`.") - } - Ok(path) => { - let path = PathBuf::from(path); - let test_context = TestContextBuilder::FromConfigPath(path).build().unwrap(); - test_context - } - }; - tracing::trace!("Running 'test_in_custom_folder_custom_333' with simplex configuration"); - test_in_custom_folder_custom_333(test_context) -} - -#[test] -fn test_in_custom_folder2_custom_333() { - assert_eq!(2 + 2, 4); -} diff --git a/simplex/crates/cli/tests/test2.rs b/simplex/crates/cli/tests/test2.rs deleted file mode 100644 index 9542e02..0000000 --- a/simplex/crates/cli/tests/test2.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::env; - -mod hello; - -#[test] -fn test_in_custom_folder_integration() { - if let Ok(value) = env::var("SIMPLEX_TEST_RUN") { - println!("hello"); - } else { - return; - } - - assert_eq!(2 + 2, 4); -} - -#[test] -fn test_in_custom_folder2_integration() { - if let Ok(value) = env::var("SIMPLEX_TEST_RUN") { - println!("hello"); - } else { - return; - } - - assert_eq!(2 + 2, 4); -} diff --git a/simplex/crates/macros-core/src/env/mod.rs b/simplex/crates/macros-core/src/env/mod.rs deleted file mode 100644 index 8adef87..0000000 --- a/simplex/crates/macros-core/src/env/mod.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::attr::SimfContent; -use crate::attr::codegen::{ - convert_contract_name_to_contract_module, convert_contract_name_to_contract_source_const, - convert_contract_name_to_struct_name, -}; -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; -use std::io::Write; -use std::path::PathBuf; -use std::{env, fs, io}; - -#[derive(thiserror::Error, Debug)] -pub enum CodeGeneratorError { - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - #[error("Failed to extract content from path, err: '{0}'")] - FailedToExtractContent(std::io::Error), - - #[error("Failed to generate file: {0}")] - GenerationFailed(String), - - #[error( - "Failed to resolve correct relative path for include_simf! macro, cwd: '{cwd:?}', simf_file: '{simf_file:?}'" - )] - FailedToFindCorrectRelativePath { cwd: PathBuf, simf_file: PathBuf }, -} - -pub struct CodeGenerator {} - -struct FileDescriptor { - simf_content: SimfContent, - simf_file: PathBuf, - out_dir: PathBuf, - cwd: PathBuf, -} - -impl<'b> CodeGenerator { - pub fn generate_files( - out_dir: impl AsRef, - simfs: &[impl AsRef], - ) -> Result<(), CodeGeneratorError> { - let out_dir = out_dir.as_ref(); - - fs::create_dir_all(out_dir)?; - - for simf_file_path in simfs { - let path_buf = PathBuf::from(simf_file_path.as_ref()); - let simf_content = SimfContent::extract_content_from_path(&path_buf) - .map_err(CodeGeneratorError::FailedToExtractContent)?; - - let output_file = out_dir.join(format!("{}.rs", simf_content.contract_name)); - - let mut file = fs::OpenOptions::new().write(true).truncate(true).open(&output_file)?; - Self::expand_file( - FileDescriptor { - simf_content, - simf_file: PathBuf::from(simf_file_path.as_ref()), - out_dir: PathBuf::from(out_dir), - cwd: env::current_dir()?, - }, - &mut file, - )?; - } - - Ok(()) - } - - fn expand_file(file_descriptor: FileDescriptor, buf: &mut dyn Write) -> Result<(), CodeGeneratorError> { - let code = Self::generate_code(file_descriptor)?; - let file: syn::File = syn::parse2(code).map_err(|e| CodeGeneratorError::GenerationFailed(e.to_string()))?; - let prettystr = prettyplease::unparse(&file); - buf.write_all(prettystr.as_bytes())?; - buf.flush()?; - Ok(()) - } - - fn generate_code(file_descriptor: FileDescriptor) -> Result { - let contract_name = &file_descriptor.simf_content.contract_name; - let program_name = { - let base_name = convert_contract_name_to_struct_name(contract_name); - format_ident!("{base_name}Program") - }; - 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 pathdiff = pathdiff::diff_paths( - &file_descriptor.simf_file.canonicalize().map_err(|e| { - io::Error::other(format!( - "Failed to canonicalize simf file descriptor, '{}', err: '{}'", - file_descriptor.simf_file.display(), - e - )) - })?, - &file_descriptor.cwd, - ) - .ok_or(CodeGeneratorError::FailedToFindCorrectRelativePath { - cwd: file_descriptor.cwd, - simf_file: file_descriptor.simf_file, - })?; - let pathdiff = format!("{}", pathdiff.display()); - - let code = quote! { - use simplex::simplex_macros::include_simf; - use simplex::simplex_sdk::program::{ArgumentsTrait, Program}; - use simplicityhl::elements::secp256k1_zkp::XOnlyPublicKey; - - pub struct #program_name { - program: Program, - } - - impl #program_name { - pub const SOURCE: &'static str = #include_simf_module::#include_simf_source_const; - - pub fn new(public_key: XOnlyPublicKey, arguments: impl ArgumentsTrait + 'static) -> Self { - Self { - program: Program::new(Self::SOURCE, public_key, Box::new(arguments)), - } - } - - pub fn get_program(&self) -> &Program { - &self.program - } - - pub fn get_program_mut(&mut self) -> &mut Program { - &mut self.program - } - } - - include_simf!(#pathdiff); - }; - - Ok(code) - } -}