From e509d1037c3234de87ff1a8d4a85fe4c4c5e1585 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Thu, 11 Jun 2026 15:54:06 +0300 Subject: [PATCH 01/10] feat: add imports support --- Cargo.lock | 35 +--- Cargo.toml | 3 +- crates/build/src/config.rs | 49 +++++ crates/build/src/error.rs | 15 ++ crates/build/src/generator.rs | 211 +++++++++++++++------- crates/build/src/lib.rs | 2 +- crates/build/src/macros/core.rs | 3 +- crates/build/src/resolver.rs | 110 +++++++++++ crates/cli/src/cli.rs | 2 +- crates/cli/src/commands/build.rs | 12 +- crates/cli/src/config/core.rs | 20 +- crates/cli/src/config/error.rs | 3 + crates/sdk/src/program/core.rs | 7 +- crates/sdk/src/program/logger.rs | 7 +- fixtures/Cargo.lock | 35 +--- fixtures/Simplex.toml | 4 + fixtures/deps/math/Simplex.toml | 28 +++ fixtures/deps/math/simf/simple_op.simf | 3 + fixtures/deps/merkle/Simplex.toml | 31 ++++ fixtures/deps/merkle/simf/build_root.simf | 13 ++ fixtures/simf/imports/multidep.simf | 16 ++ fixtures/tests/multidep_test.rs | 58 ++++++ 22 files changed, 519 insertions(+), 148 deletions(-) create mode 100644 fixtures/deps/math/Simplex.toml create mode 100644 fixtures/deps/math/simf/simple_op.simf create mode 100644 fixtures/deps/merkle/Simplex.toml create mode 100644 fixtures/deps/merkle/simf/build_root.simf create mode 100644 fixtures/simf/imports/multidep.simf create mode 100644 fixtures/tests/multidep_test.rs diff --git a/Cargo.lock b/Cargo.lock index 770c6790..26e3a21e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,18 +1005,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.14", - "regex-syntax 0.8.10", -] - [[package]] name = "regex-automata" version = "0.3.9" @@ -1128,15 +1116,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "santiago" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de36022292bc2086eb8f55bffa460fef3475e4459b478820711f4c421feb87ec" -dependencies = [ - "regex", -] - [[package]] name = "sct" version = "0.7.1" @@ -1268,9 +1247,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simplicity-lang" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e57bd4d84853974a212eab24ed89da54f49fbccf5e33e93bcd29f0a6591cd5" +checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" dependencies = [ "bitcoin", "bitcoin_hashes", @@ -1280,15 +1259,14 @@ dependencies = [ "ghost-cell", "hex-conservative", "miniscript", - "santiago", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3401ee7331f183a5458c0f5a4b3d5d00bde0fd12e2e03728c537df34efae289" +checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" dependencies = [ "bitcoin_hashes", "cc", @@ -1296,9 +1274,8 @@ dependencies = [ [[package]] name = "simplicityhl" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25de8990174fe3e1a843df138cacc4265d05839ebd2550c18b9196f567d55e81" +version = "0.6.0-rc.0" +source = "git+https://github.com/LesterEvSe/SimplicityHL.git?branch=feature%2Fdependency-builder#710a6315f4fa2dfcb1c26542a533bc2c3bf0e2b8" dependencies = [ "base64 0.21.7", "chumsky", diff --git a/Cargo.toml b/Cargo.toml index 481c8f99..aa6fd920 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,8 @@ toml = { version = "0.9.8" } minreq = { version = "2.14.1", features = ["https", "json-using-serde"] } electrsd = { version = "0.29.0", features = ["legacy"] } -simplicityhl = { version = "0.5.0" } +#simplicityhl = { version = "0.5.0" } +simplicityhl = { git = "https://github.com/LesterEvSe/SimplicityHL.git", branch = "feature/dependency-builder" } [workspace.lints.rust] rust_2018_idioms = "warn" diff --git a/crates/build/src/config.rs b/crates/build/src/config.rs index 3395e75e..67ec51b0 100644 --- a/crates/build/src/config.rs +++ b/crates/build/src/config.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::OpenOptions; use std::io::Read; use std::path::Path; @@ -38,3 +39,51 @@ impl Default for BuildConfig { } } } + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(default)] +pub struct DependencyConfig { + #[serde(flatten)] + pub inner: HashMap, +} + +impl DependencyConfig { + pub fn from_file(path: impl AsRef) -> Result { + let mut content = String::new(); + let mut file = OpenOptions::new().read(true).open(path)?; + file.read_to_string(&mut content)?; + + let table: toml::Table = toml::from_str(&content)?; + let res = if let Some(deps_section) = table.get("dependencies") { + deps_section.clone().try_into()? + } else { + DependencyConfig::default() + }; + + if let Err(err) = res.validate() { + Err(BuildError::InvalidDependency(err)) + } else { + Ok(res) + } + } + + /// When error occured, returned, return drp_name of the invalid dependency + pub fn validate(&self) -> Result<(), String> { + for (drp_name, dep) in &self.inner { + if dep.path.is_none() && dep.git.is_none() { + return Err(drp_name.clone()); + } + } + Ok(()) + } +} + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(default)] +pub struct Dependency { + /// Exact path to dir, where `Simplex.toml` file was located + pub path: Option, + + /// Link to git repo + pub git: Option, +} diff --git a/crates/build/src/error.rs b/crates/build/src/error.rs index 140b89a0..ce74f12d 100644 --- a/crates/build/src/error.rs +++ b/crates/build/src/error.rs @@ -30,4 +30,19 @@ pub enum BuildError { #[error("Failed to find prefix for a file: {0}")] NoBasePathForGeneration(#[from] std::path::StripPrefixError), + + #[error("Invalid dependency '{0}': you must specify either a 'path' or a 'git' repository")] + InvalidDependency(String), + + #[error("Dependency '{dep_name}' is missing its configuration manifest at: {expected_path}")] + MissingDependencyConfig { dep_name: String, expected_path: PathBuf }, + + #[error("{0}")] + PathCanonicalization(String), + + #[error("Failed to build dependency map: {0}")] + DependencyMap(String), + + #[error("Failed to flatten program: {0}")] + Flattening(String), } diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index 670bbff7..13750d03 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -3,9 +3,15 @@ use std::env; use std::fs; use std::io::Write; use std::path::{Component, Path, PathBuf}; +use std::sync::Arc; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use simplicityhl::TemplateProgram; +use simplicityhl::resolution::DependencyMap; +use simplicityhl::resolution::ValidatedDeps; +use simplicityhl::source::CanonPath; +use simplicityhl::source::CanonSourceFile; use crate::macros::codegen::{ convert_contract_name_to_contract_module, convert_contract_name_to_contract_source_const, @@ -17,9 +23,24 @@ use super::error::BuildError; pub struct ArtifactsGenerator {} +/// A single processed `.simf` file with all metadata needed for binding generation. +/// +/// Created once per source file and carries everything downstream — no recomputation +/// of paths or contract names in later stages. +struct SimfArtifact { + /// Path relative to `base_dir` (e.g. `hash/func/sha256.simf`). + /// Used to mirror the source structure under `out_dir/simf/`. + relative_path: PathBuf, + /// Full path to the file written under `out_dir/simf/`. + /// Passed directly to `include_simf!` — no path reconstruction needed. + mirrored_path: PathBuf, + /// Contract name extracted from the `.simf` source file. + contract_name: String, +} + #[derive(Default)] struct TreeNode { - files: Vec, + files: Vec, dirs: HashMap, } @@ -28,109 +49,174 @@ impl ArtifactsGenerator { out_dir: impl AsRef, base_dir: impl AsRef, simfs: &[impl AsRef], + validated_deps: &ValidatedDeps, ) -> Result<(), BuildError> { - let tree = Self::build_directory_tree(&base_dir, simfs)?; + let cwd = env::current_dir()?; + let out_dir = out_dir.as_ref(); + let base_dir = base_dir.as_ref(); + + let pathdiff = pathdiff::diff_paths(base_dir, &cwd).ok_or(BuildError::FailedToFindCorrectRelativePath { + cwd, + simf_file: base_dir.to_path_buf(), + })?; + + let simf_out_dir = out_dir.join(pathdiff); - Self::generate_bindings(out_dir.as_ref(), tree)?; + let artifacts = simfs + .iter() + .map(|s| Self::process_simf(s.as_ref(), base_dir, validated_deps, &simf_out_dir)) + .collect::, _>>()?; + + let tree = Self::build_tree(artifacts)?; + Self::generate_bindings(out_dir, tree)?; Ok(()) } - fn build_directory_tree(base_dir: impl AsRef, paths: &[impl AsRef]) -> Result { - let mut root = TreeNode::default(); + pub fn build_dependency_map( + validated_deps: &ValidatedDeps, + entry_root_dir: impl AsRef, + ) -> Result { + let canon_entry_root = + CanonPath::canonicalize(entry_root_dir.as_ref()).map_err(BuildError::PathCanonicalization)?; - for path in paths { - let path = path.as_ref(); + validated_deps + .with_root(canon_entry_root) + .map_err(|e| BuildError::DependencyMap(e.to_string())) + } + + /// Processes a single `.simf` source file: + /// - Writes its content to the mirrored path under `simf_out_dir` + /// - Extracts the contract name + /// + /// All path and name information needed for downstream stages is captured + /// here so later steps never need to re-derive anything. + fn process_simf( + source: &Path, + base_dir: &Path, + validated_deps: &ValidatedDeps, + simf_out_dir: &Path, + ) -> Result { + let relative_path = source + .strip_prefix(base_dir) + .map_err(BuildError::NoBasePathForGeneration)? + .to_path_buf(); + + let mirrored_path = simf_out_dir.join(&relative_path); + + if let Some(parent) = mirrored_path.parent() { + fs::create_dir_all(parent)?; + } - let relative_path = path - .strip_prefix(base_dir.as_ref()) - .map_err(BuildError::NoBasePathForGeneration)?; + let content = Self::process_content(source, validated_deps)?; + fs::write(&mirrored_path, &content)?; + + let contract_name = SimfContent::extract_content_from_path(&source.to_path_buf()) + .map_err(BuildError::FailedToExtractContent)? + .contract_name; + + Ok(SimfArtifact { + relative_path, + mirrored_path, + contract_name, + }) + } + + /// Reads and processes the content of a `.simf` file. + fn process_content(source: &Path, validated_deps: &ValidatedDeps) -> Result { + let parent_dir = source.parent().ok_or_else(|| { + BuildError::GenerationFailed(format!("Path '{}' has no parent directory", source.display())) + })?; + + let canon_source = CanonPath::canonicalize(source).map_err(BuildError::PathCanonicalization)?; + let content = fs::read_to_string(source)?; + let canon_source_file = CanonSourceFile::new(canon_source, Arc::from(content)); + let dependency_map = Self::build_dependency_map(validated_deps, parent_dir)?; + + TemplateProgram::flatten(canon_source_file, &dependency_map).map_err(BuildError::Flattening) + } + + /// Arranges a flat list of artifacts into a tree mirroring the source directory layout. + fn build_tree(artifacts: Vec) -> Result { + let mut root = TreeNode::default(); - let components: Vec<_> = relative_path + for artifact in artifacts { + let components: Vec<_> = artifact + .relative_path .components() .filter_map(|c| { if let Component::Normal(name) = c { - Some(name) + Some(name.to_string_lossy().into_owned()) } 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 dir_name = name.to_string_lossy().into_owned(); - - current_node = current_node.dirs.entry(dir_name).or_default(); - } + // All components except the last are directories; the last is the file itself + let mut current = &mut root; + for dir in &components[..components.len().saturating_sub(1)] { + current = current.dirs.entry(dir.clone()).or_default(); } + current.files.push(artifact); } Ok(root) } - fn generate_bindings(out_dir: &Path, path_tree: TreeNode) -> Result, BuildError> { + /// Recursively generates bindings for every node in the tree. + fn generate_bindings(out_dir: &Path, tree: TreeNode) -> Result<(), BuildError> { fs::create_dir_all(out_dir)?; - let mut mod_filenames = Self::generate_simfs(out_dir, &path_tree.files)?; + let mut mod_names = Vec::new(); - for (dir_name, tree_node) in path_tree.dirs.into_iter() { - Self::generate_bindings(&out_dir.join(&dir_name), tree_node)?; - mod_filenames.push(dir_name); + for artifact in tree.files { + let mod_name = Self::generate_simf_binding(out_dir, artifact)?; + mod_names.push(mod_name); } - Self::generate_mod_rs(out_dir, &mod_filenames)?; - - Ok(mod_filenames) - } - - fn generate_simfs(out_dir: impl AsRef, simfs: &[impl AsRef]) -> Result, BuildError> { - let mut module_files = Vec::with_capacity(simfs.len()); - - for simf_file_path in simfs { - let mod_name = Self::generate_simf_file(out_dir.as_ref(), simf_file_path)?; - module_files.push(mod_name); + for (dir_name, subtree) in tree.dirs { + Self::generate_bindings(&out_dir.join(&dir_name), subtree)?; + mod_names.push(dir_name); } - Ok(module_files) - } + Self::generate_mod_rs(out_dir, &mod_names)?; - fn generate_simf_file(out_dir: impl AsRef, simf_file_path: impl AsRef) -> Result { - let simf_file_buf = PathBuf::from(simf_file_path.as_ref()); - let simf_content = - SimfContent::extract_content_from_path(&simf_file_buf).map_err(BuildError::FailedToExtractContent)?; + Ok(()) + } - let contract_name = simf_content.contract_name.clone(); - let output_file = out_dir.as_ref().join(format!("{}.rs", &contract_name)); + /// Generates a single `.rs` binding file for one simf artifact. + fn generate_simf_binding(out_dir: &Path, artifact: SimfArtifact) -> Result { + let output_file = out_dir.join(format!("{}.rs", &artifact.contract_name)); let mut file = fs::OpenOptions::new() .create(true) .write(true) .truncate(true) .open(&output_file)?; - let code = Self::generate_simf_binding_code(simf_content, simf_file_buf)?; + let cwd = env::current_dir()?; + let pathdiff = + pathdiff::diff_paths(&artifact.mirrored_path, &cwd).ok_or(BuildError::FailedToFindCorrectRelativePath { + cwd, + simf_file: artifact.mirrored_path.clone(), + })?; + + let code = Self::generate_simf_binding_code(&artifact.contract_name, &pathdiff)?; Self::expand_file(code, &mut file)?; - Ok(contract_name) + Ok(artifact.contract_name) } - fn generate_mod_rs(out_dir: impl AsRef, simfs_mod_names: &[String]) -> Result<(), BuildError> { + fn generate_mod_rs(out_dir: impl AsRef, mod_names: &[String]) -> Result<(), BuildError> { let output_file = out_dir.as_ref().join("mod.rs"); let mut file = fs::OpenOptions::new() .create(true) .write(true) .truncate(true) .open(&output_file)?; - let code = Self::generate_mod_binding_code(simfs_mod_names)?; + let code = Self::generate_mod_binding_code(mod_names)?; Self::expand_file(code, &mut file)?; Ok(()) @@ -147,19 +233,15 @@ impl ArtifactsGenerator { Ok(()) } - fn generate_simf_binding_code(simf_content: SimfContent, simf_file: PathBuf) -> Result { - let cwd = env::current_dir()?; - let contract_name = &simf_content.contract_name; + fn generate_simf_binding_code(contract_name: &str, target_simf: &Path) -> Result { let program_name = { let base_name = convert_contract_name_to_struct_name(contract_name); format_ident!("{base_name}Program") }; + let 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(&simf_file, &cwd) - .ok_or(BuildError::FailedToFindCorrectRelativePath { cwd, simf_file })?; - let pathdiff = pathdiff.to_string_lossy().into_owned(); + let target_simf_str = target_simf.to_string_lossy().into_owned(); let code = quote! { use simplex::include_simf; @@ -185,14 +267,12 @@ impl ArtifactsGenerator { #[must_use] pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self { self.program = self.program.with_taproot_pubkey(pub_key); - self } #[must_use] pub fn with_storage_capacity(mut self, capacity: usize) -> Self { self.program = self.program.with_storage_capacity(capacity); - self } @@ -239,21 +319,20 @@ impl ArtifactsGenerator { } } - include_simf!(#pathdiff); + include_simf!(#target_simf_str); }; Ok(code) } fn generate_mod_binding_code(mod_names: &[String]) -> Result { - let mod_names = mod_names.iter().map(|x| format_ident!("{x}")).collect::>(); + let mod_idents = mod_names.iter().map(|x| format_ident!("{x}")).collect::>(); let code = quote! { #![allow(clippy::all)] - #( #[rustfmt::skip] - pub mod #mod_names; + pub mod #mod_idents; )* }; diff --git a/crates/build/src/lib.rs b/crates/build/src/lib.rs index 06ccd253..09d01aea 100644 --- a/crates/build/src/lib.rs +++ b/crates/build/src/lib.rs @@ -4,6 +4,6 @@ pub mod generator; pub mod macros; pub mod resolver; -pub use config::BuildConfig; +pub use config::{BuildConfig, DependencyConfig}; pub use generator::ArtifactsGenerator; pub use resolver::ArtifactsResolver; diff --git a/crates/build/src/macros/core.rs b/crates/build/src/macros/core.rs index d27d1982..969801e4 100644 --- a/crates/build/src/macros/core.rs +++ b/crates/build/src/macros/core.rs @@ -3,6 +3,7 @@ use std::error::Error; use proc_macro2::Span; use quote::quote; +use simplicityhl::ast::ElementsJetHinter; use simplicityhl::{AbiMeta, TemplateProgram}; use super::codegen::{ @@ -88,5 +89,5 @@ fn construct_argument_helpers(derived_meta: &SimfContractMeta) -> syn::Result Result> { let program = content.content.as_str(); - Ok(TemplateProgram::new(program)?.generate_abi_meta()?) + Ok(TemplateProgram::new(program, Box::new(ElementsJetHinter))?.generate_abi_meta()?) } diff --git a/crates/build/src/resolver.rs b/crates/build/src/resolver.rs index 18c9286c..42c549fd 100644 --- a/crates/build/src/resolver.rs +++ b/crates/build/src/resolver.rs @@ -1,13 +1,19 @@ +use std::collections::HashSet; use std::env; use std::path::{Path, PathBuf}; use globwalk::FileType; +use simplicityhl::resolution::{DependencyMapBuilder, ValidatedDeps}; +use simplicityhl::source::CanonPath; + +use crate::{BuildConfig, DependencyConfig}; use super::error::BuildError; pub struct ArtifactsResolver {} impl ArtifactsResolver { + // Here need to load all files, include the remappings pub fn resolve_files_to_build(src_dir: &String, simfs: &[String]) -> Result, BuildError> { let cwd = env::current_dir()?; let base = cwd.join(src_dir); @@ -24,6 +30,9 @@ impl ArtifactsResolver { paths.push(img.path().to_path_buf().canonicalize()?); } + // Resolve here + // Note! Filter out files without main function + Ok(paths) } @@ -56,4 +65,105 @@ impl ArtifactsResolver { // TODO: canonicalize? but this path may not exist Ok(path_outer) } + + /// Builds a [`ValidatedDeps`] by recursively walking the dependency tree + /// starting from the current working directory. + /// + /// Each dependency may have its own config file declaring further dependencies. + /// Those are registered with their own directory as the context, so that + /// `crate::` and sibling imports resolve correctly relative to each package root. + pub fn resolve_remappings( + deps_config: &DependencyConfig, + config_filename: &str, + ) -> Result { + let root_dir = env::current_dir()?; + let canon_root = CanonPath::canonicalize(&root_dir).map_err(BuildError::PathCanonicalization)?; + + let root_build_cfg = BuildConfig::from_file(canon_root.as_path().join(config_filename))?; + let root_simf_dir = CanonPath::canonicalize(&canon_root.as_path().join(&root_build_cfg.src_dir)) + .map_err(BuildError::PathCanonicalization)?; + + let mut collector = DepCollector { + builder: DependencyMapBuilder::new(), + visited: HashSet::new(), + config_filename: config_filename.to_string(), + }; + collector.visited.insert(canon_root.clone()); + + collector.rec_collect(deps_config, &root_simf_dir, &canon_root)?; + + collector + .builder + .validate_deps() + .map_err(|e| BuildError::DependencyMap(e.to_string())) + } +} + +/// A temporary context struct to hold global state during recursion. +/// This eliminates the need to pass `builder`, `visited`, and `config_filename` +/// into every single recursive call. +struct DepCollector { + builder: DependencyMapBuilder, + visited: HashSet, + config_filename: String, +} + +impl DepCollector { + /// Recursively registers each dependency's `simf` directory under its parent context, + /// then recurses into the dependency's own config to discover transitive dependencies. + /// + /// # Example + /// + /// Given the dependency graph: + /// ```text + /// root -> A -> B + /// root -> B + /// ``` + /// + /// Processing proceeds as follows: + /// + /// 1. Starting at `root`, register `A` as a dependency under `root`'s context, + /// then mark `root` as visited and recurse into `A`. + /// 2. Inside `A`, register `B` as a dependency under `A`'s context, mark `A` as + /// visited, and recurse into `B`. + /// 3. Inside `B`, `deps_config` is empty, so nothing is registered — recursion + /// simply returns. + /// 4. Back at `root`, register `B` as a dependency under `root`'s context as well. + /// Note that the dependency is registered *before* checking `visited` — `root` + /// must record its own link to `B`, even though `B` itself was already visited + /// and does not need to be recursed into again. + fn rec_collect( + &mut self, + deps_config: &DependencyConfig, + simf_dir: &CanonPath, + context: &CanonPath, + ) -> Result<(), BuildError> { + for (dep_name, dep) in &deps_config.inner { + let Some(dep_path_str) = dep.path.as_ref() else { + // TODO: git support + continue; + }; + + let loaded_context = CanonPath::canonicalize(&context.as_path().join(dep_path_str)) + .map_err(BuildError::PathCanonicalization)?; + + // TODO: This code could be further optimized by using a `from_str` method inside `BuildConfig` and `DependencyConfig` + let config_path = loaded_context.as_path().join(&self.config_filename); + + let loaded_src_dir = BuildConfig::from_file(&config_path)?.src_dir; + let loaded_simf_dir = CanonPath::canonicalize(&loaded_context.as_path().join(loaded_src_dir)) + .map_err(BuildError::PathCanonicalization)?; + + self.builder + .add_dependency(simf_dir.clone(), dep_name.clone(), loaded_simf_dir.clone()); + + if !self.visited.insert(loaded_context.clone()) { + continue; + } + + let nested_deps = DependencyConfig::from_file(&config_path)?; + self.rec_collect(&nested_deps, &loaded_simf_dir, &loaded_context)?; + } + Ok(()) + } } diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 69edbbba..9f4be5ca 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -70,7 +70,7 @@ impl Cli { let config_path = Config::get_default_path()?; let loaded_config = Config::load(config_path)?; - Ok(Build::run(&loaded_config.build)?) + Ok(Build::run(&loaded_config.build, &loaded_config.dependencies)?) } Command::Clean => { let config_path = Config::get_default_path()?; diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index d6d69775..95d72511 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -1,4 +1,6 @@ -use smplx_build::{ArtifactsGenerator, ArtifactsResolver, BuildConfig}; +use smplx_build::{ArtifactsGenerator, ArtifactsResolver, BuildConfig, DependencyConfig}; + +use crate::config::CONFIG_FILENAME; use super::error::CommandError; @@ -9,15 +11,21 @@ impl Build { /// /// # Errors /// Returns a `CommandError` if it fails to resolve directories or files, or if artifact generation encounters an error. - pub fn run(config: &BuildConfig) -> Result<(), CommandError> { + pub fn run(config: &BuildConfig, deps: &DependencyConfig) -> Result<(), CommandError> { let output_dir = ArtifactsResolver::resolve_local_dir(&config.out_dir)?; let src_dir = ArtifactsResolver::resolve_local_dir(&config.src_dir)?; + + // NOTE: Assume that remappings already install + let dependency_builder = ArtifactsResolver::resolve_remappings(deps, CONFIG_FILENAME)?; + + // TODO: For all remappings need to check `files_to_build` and concatenate it to `Vec` let files_to_build = ArtifactsResolver::resolve_files_to_build(&config.src_dir, &config.simf_files)?; Ok(ArtifactsGenerator::generate_artifacts( &output_dir, &src_dir, &files_to_build, + &dependency_builder, )?) } } diff --git a/crates/cli/src/config/core.rs b/crates/cli/src/config/core.rs index 882c8dfe..19d6aa39 100644 --- a/crates/cli/src/config/core.rs +++ b/crates/cli/src/config/core.rs @@ -1,7 +1,7 @@ use serde::Deserialize; use std::path::{Path, PathBuf}; -use smplx_build::BuildConfig; +use smplx_build::{BuildConfig, DependencyConfig}; use smplx_regtest::RegtestConfig; use smplx_test::TestConfig; @@ -14,6 +14,9 @@ pub const CONFIG_FILENAME: &str = "Simplex.toml"; #[serde(default)] pub struct Config { pub build: BuildConfig, + + /// Key is dependency root path name + pub dependencies: DependencyConfig, pub regtest: RegtestConfig, pub test: TestConfig, } @@ -66,18 +69,15 @@ impl Config { } fn validate(config: &Config) -> Result<(), ConfigError> { - match config.test.esplora.clone() { - Some(esplora_config) => { - Self::validate_network(&esplora_config.network)?; - - if config.test.rpc.is_some() && esplora_config.network != "ElementsRegtest" { - return Err(ConfigError::NetworkNameUnmatched(esplora_config.network.clone())); - } + if let Some(esplora_config) = config.test.esplora.clone() { + Self::validate_network(&esplora_config.network)?; - Ok(()) + if config.test.rpc.is_some() && esplora_config.network != "ElementsRegtest" { + return Err(ConfigError::NetworkNameUnmatched(esplora_config.network.clone())); } - None => Ok(()), } + + config.dependencies.validate().map_err(ConfigError::InvalidDependency) } fn validate_network(network: &String) -> Result<(), ConfigError> { diff --git a/crates/cli/src/config/error.rs b/crates/cli/src/config/error.rs index d76ca7b9..1105a648 100644 --- a/crates/cli/src/config/error.rs +++ b/crates/cli/src/config/error.rs @@ -8,6 +8,9 @@ pub enum ConfigError { #[error("TOML parse error: {0}")] TomlParse(#[from] toml::de::Error), + #[error("Invalid dependency '{0}': you must specify either a 'path' or a 'git' repository")] + InvalidDependency(String), + #[error("Network name should either be `Liquid`, `LiquidTestnet` or `ElementsRegtest`, got: {0}")] BadNetworkName(String), diff --git a/crates/sdk/src/program/core.rs b/crates/sdk/src/program/core.rs index 8c16de42..703d3955 100644 --- a/crates/sdk/src/program/core.rs +++ b/crates/sdk/src/program/core.rs @@ -4,10 +4,10 @@ use std::sync::Arc; use dyn_clone::DynClone; use simplicityhl::CompiledProgram; +use simplicityhl::ast::ElementsJetHinter; use simplicityhl::elements::pset::PartiallySignedTransaction; use simplicityhl::elements::{Address, Script, Transaction, TxOut, taproot}; use simplicityhl::simplicity::bitcoin::{XOnlyPublicKey, secp256k1}; -use simplicityhl::simplicity::jet::Elements; use simplicityhl::simplicity::jet::elements::{ElementsEnv, ElementsUtxo}; use simplicityhl::simplicity::{BitMachine, RedeemNode, Value, leaf_version}; use simplicityhl::{Parameters, WitnessTypes, WitnessValues}; @@ -62,7 +62,7 @@ pub trait ProgramTrait: DynClone { witness: &WitnessValues, input_index: usize, network: &SimplicityNetwork, - ) -> Result<(Arc>, Value), ProgramError>; + ) -> Result<(Arc, Value), ProgramError>; /// Finalizes and returns `pruned_witness` as output after executing the program on certain parameters. /// @@ -150,7 +150,7 @@ impl ProgramTrait for Program { witness: &WitnessValues, input_index: usize, network: &SimplicityNetwork, - ) -> Result<(Arc>, Value), ProgramError> { + ) -> Result<(Arc, Value), ProgramError> { let satisfied = self .load()? .satisfy(witness.clone()) @@ -312,6 +312,7 @@ impl Program { self.source, self.arguments.build_arguments(), GlobalConfig::get_include_debug_symbols(), + Box::new(ElementsJetHinter), ) .map_err(ProgramError::Compilation)?; diff --git a/crates/sdk/src/program/logger.rs b/crates/sdk/src/program/logger.rs index 5d1f1b46..1e6fe1a6 100644 --- a/crates/sdk/src/program/logger.rs +++ b/crates/sdk/src/program/logger.rs @@ -2,10 +2,7 @@ use std::{cell::RefCell, fmt::Write}; use simplicityhl::{ debug::DebugSymbols, - simplicity::{ - jet::Jet, - node::{Node, Redeem}, - }, + simplicity::node::{Node, Redeem}, tracker::{DefaultTracker, TrackerLogLevel}, }; @@ -102,7 +99,7 @@ impl ProgramLogger { /// # Safety /// Uses `transmute` to extract the inner `u32` from [`Cost`] since no public /// accessor exists. Remove once `as_milliweight()` is upstreamed to rust-simplicity. - pub fn buffer_cost_log(node: &Node>) { + pub fn buffer_cost_log(node: &Node) { let bounds = node.bounds(); // FIXME: Cost has no public accessor; remove once as_milliweight() is upstreamed let mw: u32 = unsafe { std::mem::transmute(bounds.cost) }; diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index e12d8d8b..1d7009cf 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -921,18 +921,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.14", - "regex-syntax 0.8.10", -] - [[package]] name = "regex-automata" version = "0.3.9" @@ -1044,15 +1032,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "santiago" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de36022292bc2086eb8f55bffa460fef3475e4459b478820711f4c421feb87ec" -dependencies = [ - "regex", -] - [[package]] name = "sct" version = "0.7.1" @@ -1192,9 +1171,9 @@ dependencies = [ [[package]] name = "simplicity-lang" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e57bd4d84853974a212eab24ed89da54f49fbccf5e33e93bcd29f0a6591cd5" +checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" dependencies = [ "bitcoin", "bitcoin_hashes", @@ -1204,15 +1183,14 @@ dependencies = [ "ghost-cell", "hex-conservative", "miniscript", - "santiago", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3401ee7331f183a5458c0f5a4b3d5d00bde0fd12e2e03728c537df34efae289" +checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" dependencies = [ "bitcoin_hashes", "cc", @@ -1220,9 +1198,8 @@ dependencies = [ [[package]] name = "simplicityhl" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25de8990174fe3e1a843df138cacc4265d05839ebd2550c18b9196f567d55e81" +version = "0.6.0-rc.0" +source = "git+https://github.com/LesterEvSe/SimplicityHL.git?branch=feature%2Fdependency-builder#710a6315f4fa2dfcb1c26542a533bc2c3bf0e2b8" dependencies = [ "base64 0.21.7", "chumsky", diff --git a/fixtures/Simplex.toml b/fixtures/Simplex.toml index e8eff361..7a8389d7 100644 --- a/fixtures/Simplex.toml +++ b/fixtures/Simplex.toml @@ -5,6 +5,10 @@ # simf_files = ["*.simf"] # out_dir = "./src/artifacts" +[dependencies] +merkle = { path = "deps/merkle" } +base_math = { path = "deps/math" } + # [regtest] # mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" # bitcoins = 10_000_000 diff --git a/fixtures/deps/math/Simplex.toml b/fixtures/deps/math/Simplex.toml new file mode 100644 index 00000000..e8eff361 --- /dev/null +++ b/fixtures/deps/math/Simplex.toml @@ -0,0 +1,28 @@ +# DEFAULT CONFIG + +# [build] +# src_dir = "./simf" +# simf_files = ["*.simf"] +# out_dir = "./src/artifacts" + +# [regtest] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" +# bitcoins = 10_000_000 +# rpc_port = 18443 +# esplora_port = 3000 +# rpc_user = "user" +# rpc_password = "password" + +# [test] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" +# bitcoins = 10_000_000 +# verbosity = 0 + +# [test.esplora] +# url = "" +# network = "" + +# [test.rpc] +# url = "" +# username = "" +# password = "" diff --git a/fixtures/deps/math/simf/simple_op.simf b/fixtures/deps/math/simf/simple_op.simf new file mode 100644 index 00000000..b152a361 --- /dev/null +++ b/fixtures/deps/math/simf/simple_op.simf @@ -0,0 +1,3 @@ +pub fn hash(x: u32, y: u32) -> u32 { + jet::xor_32(x, y) +} \ No newline at end of file diff --git a/fixtures/deps/merkle/Simplex.toml b/fixtures/deps/merkle/Simplex.toml new file mode 100644 index 00000000..dcd08ec7 --- /dev/null +++ b/fixtures/deps/merkle/Simplex.toml @@ -0,0 +1,31 @@ +# DEFAULT CONFIG + +#[build] +# src_dir = "./simf" +# simf_files = ["*.simf"] +# out_dir = "./src/artifacts" + +[dependencies] +math = { path = "../math" } + +# [regtest] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" +# bitcoins = 10_000_000 +# rpc_port = 18443 +# esplora_port = 3000 +# rpc_user = "user" +# rpc_password = "password" + +# [test] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" +# bitcoins = 10_000_000 +# verbosity = 0 + +# [test.esplora] +# url = "" +# network = "" + +# [test.rpc] +# url = "" +# username = "" +# password = "" diff --git a/fixtures/deps/merkle/simf/build_root.simf b/fixtures/deps/merkle/simf/build_root.simf new file mode 100644 index 00000000..7b01945f --- /dev/null +++ b/fixtures/deps/merkle/simf/build_root.simf @@ -0,0 +1,13 @@ +use math::simple_op::hash as temp_hash; + +//pub mod wrapper { + pub fn get_root(tx1: u32, tx2: u32) -> u32 { + temp_hash(tx1, tx2) + } + + pub fn hash(tx1: u32, tx2: u32) -> u32 { + jet::and_32(tx1, tx2) + } +//} + +//pub use crate::wrapper::{get_root, hash}; \ No newline at end of file diff --git a/fixtures/simf/imports/multidep.simf b/fixtures/simf/imports/multidep.simf new file mode 100644 index 00000000..29d1f621 --- /dev/null +++ b/fixtures/simf/imports/multidep.simf @@ -0,0 +1,16 @@ +use merkle::build_root::{get_root, hash as and_hash}; +use base_math::simple_op::hash as or_hash; + +pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { + let root: u32 = get_root(tx1, tx2); + or_hash(prev_hash, root) +} + +fn main() { + let block_val_hash: u32 = get_block_value_hash(param::PREV_HASH, 10, 20); + assert!(jet::eq_32(block_val_hash, 27)); + + let first_value: u32 = witness::TX1; + let second_value: u32 = 22; + assert!(jet::eq_32(and_hash(first_value, second_value), 6)); +} \ No newline at end of file diff --git a/fixtures/tests/multidep_test.rs b/fixtures/tests/multidep_test.rs new file mode 100644 index 00000000..52b86684 --- /dev/null +++ b/fixtures/tests/multidep_test.rs @@ -0,0 +1,58 @@ +use simplex::simplicityhl::elements::Script; + +use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature}; + +use simplex_fixtures::artifacts::imports::multidep::MultidepProgram; +use simplex_fixtures::artifacts::imports::multidep::derived_multidep::{MultidepArguments, MultidepWitness}; + +fn get_multidep(context: &simplex::TestContext) -> (MultidepProgram, Script) { + let arguments = MultidepArguments { prev_hash: 5 }; + + let p2pk = MultidepProgram::new(arguments); + let p2pk_script = p2pk.get_script_pubkey(context.get_network()); + + (p2pk, p2pk_script) +} + +fn spend_p2wpkh(context: &simplex::TestContext) -> anyhow::Result<()> { + let signer = context.get_default_signer(); + + let (_, script) = get_multidep(context); + + let tx_receipt = signer.send(script.clone(), 50)?; + println!("Broadcast: {}", tx_receipt); + + Ok(()) +} + +fn spend_p2pk(context: &simplex::TestContext) -> anyhow::Result<()> { + let signer = context.get_default_signer(); + let provider = context.get_default_provider(); + + let (program, script) = get_multidep(context); + + let utxos = provider.fetch_scripthash_utxos(&script)?; + + let mut ft = FinalTransaction::new(); + + let witness = MultidepWitness { tx1: 15 }; + + ft.add_program_input( + PartialInput::new(utxos[0].clone()), + ProgramInput::new(Box::new(program.as_ref().clone()), Box::new(witness.clone())), + RequiredSignature::None, + ); + + let tx_receipt = signer.broadcast(&ft)?; + println!("Broadcast: {}", tx_receipt); + + Ok(()) +} + +#[simplex::test] +fn multidep_test(context: simplex::TestContext) -> anyhow::Result<()> { + spend_p2wpkh(&context)?; + spend_p2pk(&context)?; + + Ok(()) +} From 3e90f31f71dfdca48e96340397b52064d486ab5c Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Fri, 12 Jun 2026 19:10:18 +0300 Subject: [PATCH 02/10] feat: filter files with `fn main` --- Cargo.lock | 13 +++++++++++ crates/build/Cargo.toml | 1 + crates/build/src/resolver.rs | 22 ++++++++++++++----- fixtures/Cargo.lock | 13 +++++++++++ fixtures/Simplex.toml | 4 ++-- fixtures/{ => simf}/deps/math/Simplex.toml | 0 .../{ => simf}/deps/math/simf/simple_op.simf | 0 fixtures/{ => simf}/deps/merkle/Simplex.toml | 0 .../deps/merkle/simf/build_root.simf | 0 9 files changed, 46 insertions(+), 7 deletions(-) rename fixtures/{ => simf}/deps/math/Simplex.toml (100%) rename fixtures/{ => simf}/deps/math/simf/simple_op.simf (100%) rename fixtures/{ => simf}/deps/merkle/Simplex.toml (100%) rename fixtures/{ => simf}/deps/merkle/simf/build_root.simf (100%) diff --git a/Cargo.lock b/Cargo.lock index 26e3a21e..ad591afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,6 +1005,18 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1305,6 +1317,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", + "regex", "serde", "simplicityhl", "syn", diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index 8487fbfe..e1581621 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -14,6 +14,7 @@ toml = { workspace = true } serde = { workspace = true } simplicityhl = { workspace = true } +regex = "1" 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" } diff --git a/crates/build/src/resolver.rs b/crates/build/src/resolver.rs index 42c549fd..e1a1bd5b 100644 --- a/crates/build/src/resolver.rs +++ b/crates/build/src/resolver.rs @@ -1,8 +1,10 @@ use std::collections::HashSet; use std::env; use std::path::{Path, PathBuf}; +use std::sync::OnceLock; use globwalk::FileType; +use regex::Regex; use simplicityhl::resolution::{DependencyMapBuilder, ValidatedDeps}; use simplicityhl::source::CanonPath; @@ -13,7 +15,6 @@ use super::error::BuildError; pub struct ArtifactsResolver {} impl ArtifactsResolver { - // Here need to load all files, include the remappings pub fn resolve_files_to_build(src_dir: &String, simfs: &[String]) -> Result, BuildError> { let cwd = env::current_dir()?; let base = cwd.join(src_dir); @@ -27,11 +28,13 @@ impl ArtifactsResolver { .filter_map(Result::ok); for img in walker { - paths.push(img.path().to_path_buf().canonicalize()?); - } + let path = img.path().to_path_buf().canonicalize()?; + let content = std::fs::read_to_string(&path)?; - // Resolve here - // Note! Filter out files without main function + if Self::contains_main(&content) { + paths.push(path); + } + } Ok(paths) } @@ -97,6 +100,15 @@ impl ArtifactsResolver { .validate_deps() .map_err(|e| BuildError::DependencyMap(e.to_string())) } + + /// Checks whether the source contains a `fn main(...)` declaration, + /// regardless of visibility (`pub fn main` or `fn main`) or nesting + /// inside `mod { ... }` blocks; The regex matches anywhere in the text. + fn contains_main(source: &str) -> bool { + static RE: OnceLock = OnceLock::new(); + let re = RE.get_or_init(|| Regex::new(r"(?m)^\s*(pub\s+)?fn\s+main\s*\(").unwrap()); + re.is_match(source) + } } /// A temporary context struct to hold global state during recursion. diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index 1d7009cf..7684466e 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -921,6 +921,18 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -1229,6 +1241,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", + "regex", "serde", "simplicityhl", "syn", diff --git a/fixtures/Simplex.toml b/fixtures/Simplex.toml index 7a8389d7..7a272b7b 100644 --- a/fixtures/Simplex.toml +++ b/fixtures/Simplex.toml @@ -6,8 +6,8 @@ # out_dir = "./src/artifacts" [dependencies] -merkle = { path = "deps/merkle" } -base_math = { path = "deps/math" } +merkle = { path = "simf/deps/merkle" } +base_math = { path = "simf/deps/math" } # [regtest] # mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" diff --git a/fixtures/deps/math/Simplex.toml b/fixtures/simf/deps/math/Simplex.toml similarity index 100% rename from fixtures/deps/math/Simplex.toml rename to fixtures/simf/deps/math/Simplex.toml diff --git a/fixtures/deps/math/simf/simple_op.simf b/fixtures/simf/deps/math/simf/simple_op.simf similarity index 100% rename from fixtures/deps/math/simf/simple_op.simf rename to fixtures/simf/deps/math/simf/simple_op.simf diff --git a/fixtures/deps/merkle/Simplex.toml b/fixtures/simf/deps/merkle/Simplex.toml similarity index 100% rename from fixtures/deps/merkle/Simplex.toml rename to fixtures/simf/deps/merkle/Simplex.toml diff --git a/fixtures/deps/merkle/simf/build_root.simf b/fixtures/simf/deps/merkle/simf/build_root.simf similarity index 100% rename from fixtures/deps/merkle/simf/build_root.simf rename to fixtures/simf/deps/merkle/simf/build_root.simf From 808cb35b53c2405031421a088cc68cf110cdd893 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Mon, 15 Jun 2026 18:17:55 +0300 Subject: [PATCH 03/10] fix: apply linting suggestions from review --- Cargo.toml | 2 +- crates/build/Cargo.toml | 2 +- crates/build/src/config.rs | 34 +++++++++---------- crates/build/src/generator.rs | 5 +++ crates/build/src/resolver.rs | 3 ++ fixtures/simf/deps/math/simf/simple_op.simf | 2 +- .../simf/deps/merkle/simf/build_root.simf | 2 +- fixtures/simf/imports/multidep.simf | 2 +- 8 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa6fd920..1b54dea3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ toml = { version = "0.9.8" } minreq = { version = "2.14.1", features = ["https", "json-using-serde"] } electrsd = { version = "0.29.0", features = ["legacy"] } -#simplicityhl = { version = "0.5.0" } +# simplicityhl = { version = "0.5.0" } simplicityhl = { git = "https://github.com/LesterEvSe/SimplicityHL.git", branch = "feature/dependency-builder" } [workspace.lints.rust] diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index e1581621..fc389b1a 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -14,7 +14,7 @@ toml = { workspace = true } serde = { workspace = true } simplicityhl = { workspace = true } -regex = "1" +regex = { version = "1" } 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" } diff --git a/crates/build/src/config.rs b/crates/build/src/config.rs index 67ec51b0..7422284a 100644 --- a/crates/build/src/config.rs +++ b/crates/build/src/config.rs @@ -19,6 +19,23 @@ pub struct BuildConfig { pub out_dir: String, } +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(default)] +pub struct DependencyConfig { + #[serde(flatten)] + pub inner: HashMap, +} + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(default)] +pub struct Dependency { + /// Exact path to dir, where `Simplex.toml` file was located + pub path: Option, + + /// Link to git repo + pub git: Option, +} + impl BuildConfig { pub fn from_file(path: impl AsRef) -> Result { let mut content = String::new(); @@ -40,13 +57,6 @@ impl Default for BuildConfig { } } -#[derive(Debug, Default, Clone, Deserialize)] -#[serde(default)] -pub struct DependencyConfig { - #[serde(flatten)] - pub inner: HashMap, -} - impl DependencyConfig { pub fn from_file(path: impl AsRef) -> Result { let mut content = String::new(); @@ -77,13 +87,3 @@ impl DependencyConfig { Ok(()) } } - -#[derive(Debug, Default, Clone, Deserialize)] -#[serde(default)] -pub struct Dependency { - /// Exact path to dir, where `Simplex.toml` file was located - pub path: Option, - - /// Link to git repo - pub git: Option, -} diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index 13750d03..d84291c5 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use proc_macro2::TokenStream; use quote::{format_ident, quote}; + use simplicityhl::TemplateProgram; use simplicityhl::resolution::DependencyMap; use simplicityhl::resolution::ValidatedDeps; @@ -68,6 +69,7 @@ impl ArtifactsGenerator { .collect::, _>>()?; let tree = Self::build_tree(artifacts)?; + Self::generate_bindings(out_dir, tree)?; Ok(()) @@ -158,6 +160,7 @@ impl ArtifactsGenerator { for dir in &components[..components.len().saturating_sub(1)] { current = current.dirs.entry(dir.clone()).or_default(); } + current.files.push(artifact); } @@ -203,6 +206,7 @@ impl ArtifactsGenerator { })?; let code = Self::generate_simf_binding_code(&artifact.contract_name, &pathdiff)?; + Self::expand_file(code, &mut file)?; Ok(artifact.contract_name) @@ -217,6 +221,7 @@ impl ArtifactsGenerator { .open(&output_file)?; let code = Self::generate_mod_binding_code(mod_names)?; + Self::expand_file(code, &mut file)?; Ok(()) diff --git a/crates/build/src/resolver.rs b/crates/build/src/resolver.rs index e1a1bd5b..682da8d6 100644 --- a/crates/build/src/resolver.rs +++ b/crates/build/src/resolver.rs @@ -5,6 +5,7 @@ use std::sync::OnceLock; use globwalk::FileType; use regex::Regex; + use simplicityhl::resolution::{DependencyMapBuilder, ValidatedDeps}; use simplicityhl::source::CanonPath; @@ -107,6 +108,7 @@ impl ArtifactsResolver { fn contains_main(source: &str) -> bool { static RE: OnceLock = OnceLock::new(); let re = RE.get_or_init(|| Regex::new(r"(?m)^\s*(pub\s+)?fn\s+main\s*\(").unwrap()); + re.is_match(source) } } @@ -176,6 +178,7 @@ impl DepCollector { let nested_deps = DependencyConfig::from_file(&config_path)?; self.rec_collect(&nested_deps, &loaded_simf_dir, &loaded_context)?; } + Ok(()) } } diff --git a/fixtures/simf/deps/math/simf/simple_op.simf b/fixtures/simf/deps/math/simf/simple_op.simf index b152a361..28e3e2c3 100644 --- a/fixtures/simf/deps/math/simf/simple_op.simf +++ b/fixtures/simf/deps/math/simf/simple_op.simf @@ -1,3 +1,3 @@ pub fn hash(x: u32, y: u32) -> u32 { jet::xor_32(x, y) -} \ No newline at end of file +} diff --git a/fixtures/simf/deps/merkle/simf/build_root.simf b/fixtures/simf/deps/merkle/simf/build_root.simf index 7b01945f..4075d31e 100644 --- a/fixtures/simf/deps/merkle/simf/build_root.simf +++ b/fixtures/simf/deps/merkle/simf/build_root.simf @@ -10,4 +10,4 @@ use math::simple_op::hash as temp_hash; } //} -//pub use crate::wrapper::{get_root, hash}; \ No newline at end of file +//pub use crate::wrapper::{get_root, hash}; diff --git a/fixtures/simf/imports/multidep.simf b/fixtures/simf/imports/multidep.simf index 29d1f621..b13d74c5 100644 --- a/fixtures/simf/imports/multidep.simf +++ b/fixtures/simf/imports/multidep.simf @@ -13,4 +13,4 @@ fn main() { let first_value: u32 = witness::TX1; let second_value: u32 = 22; assert!(jet::eq_32(and_hash(first_value, second_value), 6)); -} \ No newline at end of file +} From 53a63aecdd6027af9cca0207653a52aeeb75617b Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 16 Jun 2026 14:06:09 +0300 Subject: [PATCH 04/10] feat: add dependency installation. Refactor code a bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/build/src/config.rs | 59 +++++---- crates/build/src/error.rs | 19 ++- crates/build/src/generator.rs | 17 +++ crates/build/src/resolver.rs | 53 +++++--- crates/cli/src/cli.rs | 7 ++ crates/cli/src/commands/core.rs | 2 + crates/cli/src/commands/error.rs | 24 ++++ crates/cli/src/commands/install.rs | 114 ++++++++++++++++++ crates/cli/src/commands/mod.rs | 1 + crates/cli/src/config/core.rs | 3 +- crates/cli/src/config/error.rs | 6 +- crates/sdk/src/program/logger.rs | 11 +- fixtures/.gitignore | 2 +- fixtures/Cargo.lock | 2 +- fixtures/Simplex.toml | 12 +- .../Simplex.toml | 32 +++++ .../examples/array_fold.simf | 10 ++ .../examples/array_fold_2n.simf | 12 ++ .../examples/cat.simf | 8 ++ .../examples/ctv.simf | 22 ++++ .../examples/escrow_with_delay.simf | 62 ++++++++++ .../examples/escrow_with_delay.timeout.wit | 6 + .../examples/hash_loop.simf | 32 +++++ .../examples/hodl_vault.simf | 42 +++++++ .../examples/hodl_vault.wit | 18 +++ .../examples/htlc.complete.wit | 6 + .../examples/htlc.simf | 46 +++++++ .../examples/last_will.inherit.wit | 6 + .../examples/last_will.simf | 51 ++++++++ .../examples/local_crate/main.simf | 5 + .../examples/local_crate/math.simf | 4 + .../examples/modules.simf | 26 ++++ .../examples/multiple_deps/flattened.simf | 34 ++++++ .../examples/multiple_deps/main.simf | 16 +++ .../multiple_deps/math/simple_op.simf | 3 + .../multiple_deps/merkle/build_root.simf | 9 ++ .../simple_multidep/crypto/hashes.simf | 5 + .../examples/simple_multidep/flattened.simf | 24 ++++ .../examples/simple_multidep/main.simf | 7 ++ .../simple_multidep/math/arithmetic.simf | 4 + .../examples/single_dep/flattened.simf | 29 +++++ .../examples/single_dep/main.simf | 11 ++ .../single_dep/temp/constants/utils.simf | 5 + .../examples/single_dep/temp/funcs.simf | 5 + fixtures/{simf => }/deps/math/Simplex.toml | 0 .../{simf => }/deps/math/simf/simple_op.simf | 0 fixtures/{simf => }/deps/merkle/Simplex.toml | 2 +- fixtures/deps/merkle/simf/build_root.simf | 14 +++ .../Simplex.toml | 30 +++++ .../test/smth.simf | 1 + .../simf/deps/merkle/simf/build_root.simf | 13 -- fixtures/simf/imports/multidep.simf | 2 +- fixtures/simf/p2pk.simf | 2 +- 55 files changed, 860 insertions(+), 80 deletions(-) create mode 100644 crates/cli/src/commands/install.rs create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/Simplex.toml create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/math/simple_op.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf create mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf rename fixtures/{simf => }/deps/math/Simplex.toml (100%) rename fixtures/{simf => }/deps/math/simf/simple_op.simf (100%) rename fixtures/{simf => }/deps/merkle/Simplex.toml (98%) create mode 100644 fixtures/deps/merkle/simf/build_root.simf create mode 100644 fixtures/deps/test-simplex-crate-3a0ece279ca58451/Simplex.toml create mode 100644 fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf delete mode 100644 fixtures/simf/deps/merkle/simf/build_root.simf diff --git a/Cargo.lock b/Cargo.lock index ad591afa..c1efd224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "simplicityhl" version = "0.6.0-rc.0" -source = "git+https://github.com/LesterEvSe/SimplicityHL.git?branch=feature%2Fdependency-builder#710a6315f4fa2dfcb1c26542a533bc2c3bf0e2b8" +source = "git+https://github.com/BlockstreamResearch/SimplicityHL.git#2cf1f8e5c6af9c34f81e13e2633156657dea9d0e" dependencies = [ "base64 0.21.7", "chumsky", diff --git a/Cargo.toml b/Cargo.toml index 1b54dea3..382b1744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ minreq = { version = "2.14.1", features = ["https", "json-using-serde"] } electrsd = { version = "0.29.0", features = ["legacy"] } # simplicityhl = { version = "0.5.0" } -simplicityhl = { git = "https://github.com/LesterEvSe/SimplicityHL.git", branch = "feature/dependency-builder" } +simplicityhl = { git = "https://github.com/BlockstreamResearch/SimplicityHL.git" } [workspace.lints.rust] rust_2018_idioms = "warn" diff --git a/crates/build/src/config.rs b/crates/build/src/config.rs index 7422284a..4d144f90 100644 --- a/crates/build/src/config.rs +++ b/crates/build/src/config.rs @@ -1,15 +1,14 @@ use std::collections::HashMap; -use std::fs::OpenOptions; -use std::io::Read; -use std::path::Path; use serde::Deserialize; use super::error::BuildError; +use super::error::DependencyValidationError; pub const DEFAULT_OUT_DIR_NAME: &str = "src/artifacts"; pub const DEFAULT_INCLUDE_PATH: &str = "**/*.simf"; pub const DEFAULT_SRC_DIR_NAME: &str = "simf"; +pub const DEFAULT_DEPENDENCY_DIR: &str = "deps"; #[derive(Debug, Clone, Deserialize)] #[serde(default)] @@ -37,13 +36,16 @@ pub struct Dependency { } impl BuildConfig { - pub fn from_file(path: impl AsRef) -> Result { - let mut content = String::new(); - let mut file = OpenOptions::new().read(true).open(path)?; - - file.read_to_string(&mut content)?; - - Ok(toml::from_str(&content)?) + /// Parses the `[build]` section from TOML source text. + /// + /// The `[build]` table is nested, so this descends into it rather than reading + /// top-level keys. If the section is absent, returns [`BuildConfig::default`]. + pub fn from_source(content: &str) -> Result { + let table: toml::Table = toml::from_str(content)?; + match table.get("build") { + Some(section) => Ok(section.clone().try_into()?), + None => Ok(Self::default()), + } } } @@ -58,30 +60,27 @@ impl Default for BuildConfig { } impl DependencyConfig { - pub fn from_file(path: impl AsRef) -> Result { - let mut content = String::new(); - let mut file = OpenOptions::new().read(true).open(path)?; - file.read_to_string(&mut content)?; - - let table: toml::Table = toml::from_str(&content)?; - let res = if let Some(deps_section) = table.get("dependencies") { - deps_section.clone().try_into()? - } else { - DependencyConfig::default() + /// Parses and validates the `[dependencies]` section from TOML source text. + /// + /// The `[dependencies]` table is nested, so this descends into it rather than + /// reading top-level keys. If the section is absent, returns the default + /// (empty) config. Each dependency is validated to declare exactly one source. + pub fn from_source(content: &str) -> Result { + let table: toml::Table = toml::from_str(content)?; + let res: Self = match table.get("dependencies") { + Some(section) => section.clone().try_into()?, + None => Self::default(), }; - - if let Err(err) = res.validate() { - Err(BuildError::InvalidDependency(err)) - } else { - Ok(res) - } + res.validate()?; + Ok(res) } - /// When error occured, returned, return drp_name of the invalid dependency - pub fn validate(&self) -> Result<(), String> { + pub fn validate(&self) -> Result<(), DependencyValidationError> { for (drp_name, dep) in &self.inner { - if dep.path.is_none() && dep.git.is_none() { - return Err(drp_name.clone()); + match (&dep.path, &dep.git) { + (None, None) => return Err(DependencyValidationError::Missing(drp_name.clone())), + (Some(_), Some(_)) => return Err(DependencyValidationError::Conflicting(drp_name.clone())), + _ => {} } } Ok(()) diff --git a/crates/build/src/error.rs b/crates/build/src/error.rs index ce74f12d..3b57048e 100644 --- a/crates/build/src/error.rs +++ b/crates/build/src/error.rs @@ -3,6 +3,18 @@ use std::path::PathBuf; use globwalk::GlobError; +/// Neutral validation error for a single dependency declaration. +/// +/// Returned by `DependencyConfig::validate` so it stays decoupled from any +/// specific crate's error type. Each caller converts it into their own variant. +#[derive(thiserror::Error, Debug)] +pub enum DependencyValidationError { + #[error("Invalid dependency '{0}': you must specify either a 'path' or a 'git' repository")] + Missing(String), + #[error("Invalid dependency '{0}': cannot specify both 'path' and 'git', choose one")] + Conflicting(String), +} + #[derive(thiserror::Error, Debug)] pub enum BuildError { #[error("IO error: {0}")] @@ -31,8 +43,8 @@ pub enum BuildError { #[error("Failed to find prefix for a file: {0}")] NoBasePathForGeneration(#[from] std::path::StripPrefixError), - #[error("Invalid dependency '{0}': you must specify either a 'path' or a 'git' repository")] - InvalidDependency(String), + #[error(transparent)] + DependencyValidation(#[from] DependencyValidationError), #[error("Dependency '{dep_name}' is missing its configuration manifest at: {expected_path}")] MissingDependencyConfig { dep_name: String, expected_path: PathBuf }, @@ -45,4 +57,7 @@ pub enum BuildError { #[error("Failed to flatten program: {0}")] Flattening(String), + + #[error("Invalid git repository URL: '{0}'")] + InvalidGitUrl(String), } diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index d84291c5..fcab7f1b 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::env; use std::fs; +use std::hash::{DefaultHasher, Hash as _, Hasher as _}; use std::io::Write; use std::path::{Component, Path, PathBuf}; use std::sync::Arc; @@ -343,4 +344,20 @@ impl ArtifactsGenerator { Ok(code) } + + /// Converts "https://github.com/smplx/core.git" + /// into a Cargo-style path: "core-a1b2c3d4e5f67890" + pub fn generate_hashed_repo_path(url: &str) -> Option { + let clean_url = url.strip_suffix(".git").unwrap_or(url); + let repo_name = clean_url.split('/').next_back()?; + + let mut hasher = DefaultHasher::new(); + url.hash(&mut hasher); + let hash_value = hasher.finish(); + + // Do it the Rust way: EXACTLY 16 hex characters + let folder_name = format!("{}-{:016x}", repo_name, hash_value); + + Some(PathBuf::from(folder_name)) + } } diff --git a/crates/build/src/resolver.rs b/crates/build/src/resolver.rs index 682da8d6..8a6995a2 100644 --- a/crates/build/src/resolver.rs +++ b/crates/build/src/resolver.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; -use std::env; use std::path::{Path, PathBuf}; use std::sync::OnceLock; +use std::{env, fs}; use globwalk::FileType; use regex::Regex; @@ -9,7 +9,8 @@ use regex::Regex; use simplicityhl::resolution::{DependencyMapBuilder, ValidatedDeps}; use simplicityhl::source::CanonPath; -use crate::{BuildConfig, DependencyConfig}; +use crate::config::{DEFAULT_DEPENDENCY_DIR, Dependency}; +use crate::{ArtifactsGenerator, BuildConfig, DependencyConfig}; use super::error::BuildError; @@ -83,14 +84,21 @@ impl ArtifactsResolver { let root_dir = env::current_dir()?; let canon_root = CanonPath::canonicalize(&root_dir).map_err(BuildError::PathCanonicalization)?; - let root_build_cfg = BuildConfig::from_file(canon_root.as_path().join(config_filename))?; - let root_simf_dir = CanonPath::canonicalize(&canon_root.as_path().join(&root_build_cfg.src_dir)) + let config_source = fs::read_to_string(canon_root.as_path().join(config_filename))?; + let root_src_dir = BuildConfig::from_source(&config_source)?.src_dir; + let root_simf_dir = CanonPath::canonicalize(&canon_root.as_path().join(&root_src_dir)) .map_err(BuildError::PathCanonicalization)?; + // Flat install dir shared by every git dependency at any nesting depth, + // mirroring `install`. Left un-canonicalized so pure-path projects + // (which never create `deps/`) don't fail here. + let deps_dir = PathBuf::from(DEFAULT_DEPENDENCY_DIR); + let mut collector = DepCollector { builder: DependencyMapBuilder::new(), visited: HashSet::new(), config_filename: config_filename.to_string(), + deps_dir, }; collector.visited.insert(canon_root.clone()); @@ -120,6 +128,7 @@ struct DepCollector { builder: DependencyMapBuilder, visited: HashSet, config_filename: String, + deps_dir: PathBuf, } impl DepCollector { @@ -153,18 +162,12 @@ impl DepCollector { context: &CanonPath, ) -> Result<(), BuildError> { for (dep_name, dep) in &deps_config.inner { - let Some(dep_path_str) = dep.path.as_ref() else { - // TODO: git support - continue; - }; - - let loaded_context = CanonPath::canonicalize(&context.as_path().join(dep_path_str)) - .map_err(BuildError::PathCanonicalization)?; + let loaded_context = self.resolve_dep_context(dep, context)?; - // TODO: This code could be further optimized by using a `from_str` method inside `BuildConfig` and `DependencyConfig` let config_path = loaded_context.as_path().join(&self.config_filename); + let config_source = fs::read_to_string(config_path)?; - let loaded_src_dir = BuildConfig::from_file(&config_path)?.src_dir; + let loaded_src_dir = BuildConfig::from_source(&config_source)?.src_dir; let loaded_simf_dir = CanonPath::canonicalize(&loaded_context.as_path().join(loaded_src_dir)) .map_err(BuildError::PathCanonicalization)?; @@ -175,10 +178,32 @@ impl DepCollector { continue; } - let nested_deps = DependencyConfig::from_file(&config_path)?; + let nested_deps = DependencyConfig::from_source(&config_source)?; self.rec_collect(&nested_deps, &loaded_simf_dir, &loaded_context)?; } Ok(()) } + + /// Resolves the on-disk package root for a single dependency. + /// + /// - `path` deps resolve relative to the *parent* package (`context`). + /// - `git` deps resolve into the flat root install dir (`self.deps_dir`). + /// reusing the exact hashed directory name produced by `install`. + fn resolve_dep_context(&self, dep: &Dependency, context: &CanonPath) -> Result { + let raw_path = match (&dep.path, &dep.git) { + (Some(path), None) => context.as_path().join(path), + (None, Some(git_url)) => { + let hashed = ArtifactsGenerator::generate_hashed_repo_path(git_url) + .ok_or_else(|| BuildError::InvalidGitUrl(git_url.clone()))?; + self.deps_dir.join(hashed) + } + // `DependencyConfig::validate` guarantees exactly one of path/git is set. + (Some(_), Some(_)) | (None, None) => { + unreachable!("dependency source validated in 'DependencyConfig::validate'") + } + }; + + CanonPath::canonicalize(&raw_path).map_err(BuildError::PathCanonicalization) + } } diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 9f4be5ca..363c28ec 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -6,6 +6,7 @@ use crate::commands::Command; use crate::commands::build::Build; use crate::commands::clean::Clean; use crate::commands::init::Init; +use crate::commands::install::Install; use crate::commands::regtest::Regtest; use crate::commands::test::Test; use crate::config::Config; @@ -66,6 +67,12 @@ impl Cli { Ok(Regtest::run(&loaded_config.regtest)?) } + Command::Install => { + let config_path = Config::get_default_path()?; + let loaded_config = Config::load(config_path)?; + + Ok(Install::run(&loaded_config.dependencies)?) + } Command::Build => { let config_path = Config::get_default_path()?; let loaded_config = Config::load(config_path)?; diff --git a/crates/cli/src/commands/core.rs b/crates/cli/src/commands/core.rs index 52d96dae..3a717308 100644 --- a/crates/cli/src/commands/core.rs +++ b/crates/cli/src/commands/core.rs @@ -19,6 +19,8 @@ pub enum Command { #[command(flatten)] flags: TestFlags, }, + /// Install the simplicity dependency + Install, /// Generates the simplicity contracts artifacts Build, /// Cleans Simplex artifacts in the current directory diff --git a/crates/cli/src/commands/error.rs b/crates/cli/src/commands/error.rs index b9f3d193..b6996024 100644 --- a/crates/cli/src/commands/error.rs +++ b/crates/cli/src/commands/error.rs @@ -20,6 +20,9 @@ pub enum CommandError { #[error(transparent)] Clean(#[from] CleanError), + #[error(transparent)] + Install(#[from] InstallError), + #[error("IO error: {0}")] Io(#[from] std::io::Error), @@ -65,3 +68,24 @@ pub enum CleanError { #[error("Failed to remove file '{1}': {0}")] RemoveFile(std::io::Error, PathBuf), } + +#[derive(thiserror::Error, Debug)] +pub enum InstallError { + #[error(transparent)] + Config(#[from] crate::config::error::ConfigError), + + #[error("Invalid Git repository URL (cannot extract name): '{0}'")] + InvalidUrl(String), + + #[error("Failed to create directory at '{1}': {0}")] + CreateDir(std::io::Error, PathBuf), + + #[error("Failed to read directory at '{1}': {0}")] + ReadDir(std::io::Error, PathBuf), + + #[error("Failed to execute git process for '{1}': {0}")] + GitExecution(std::io::Error, String), + + #[error("Git clone command failed for repository: '{0}'")] + GitCloneFailed(String), +} diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs new file mode 100644 index 00000000..2cd2a748 --- /dev/null +++ b/crates/cli/src/commands/install.rs @@ -0,0 +1,114 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::path::Path; +use std::process::Command; +use std::{fs, path::PathBuf}; + +use smplx_build::config::DEFAULT_DEPENDENCY_DIR; +use smplx_build::{ArtifactsGenerator, DependencyConfig}; + +use crate::commands::error::CommandError; +use crate::commands::error::InstallError; +use crate::config::Config; + +pub struct Install; + +pub struct InstalledRepos(Vec); + +impl Install { + /// Installs all git-based dependencies into the local [`DEFAULT_DEPENDENCY_DIR`] directory. + /// + /// Clones each dependency declared in [`DEFAULT_DEPENDENCY_DIR`] that specifies a `git` source, + /// then reports which repositories were installed. + /// + /// # Errors + /// + /// Returns a [`CommandError`] if: + /// - The [`DEFAULT_DEPENDENCY_DIR`] directory cannot be created (e.g. permission denied). + /// - Any repository fails to clone or install. + pub fn run(deps: &DependencyConfig) -> Result<(), CommandError> { + let mut installed_repos = Vec::::new(); + + let deps_dir = Path::new(DEFAULT_DEPENDENCY_DIR); + fs::create_dir_all(DEFAULT_DEPENDENCY_DIR)?; + + Self::install_repos(deps, deps_dir, &mut installed_repos)?; + let installed_repos = InstalledRepos(installed_repos); + + if installed_repos.0.is_empty() { + println!("No repositories were installed."); + } else { + println!("Installed repositories: {installed_repos}"); + } + + Ok(()) + } + + fn install_repos( + deps: &DependencyConfig, + deps_dir: &Path, + installed_repos: &mut Vec, + ) -> Result<(), InstallError> { + for dependency in deps.inner.values() { + let Some(git_repo_url) = &dependency.git else { + continue; + }; + + let hashed_folder = ArtifactsGenerator::generate_hashed_repo_path(git_repo_url) + .ok_or_else(|| InstallError::InvalidUrl(git_repo_url.clone()))?; + + let target_dir = deps_dir.join(hashed_folder); + + if !target_dir.exists() { + fs::create_dir_all(&target_dir).map_err(|e| InstallError::CreateDir(e, target_dir.clone()))?; + } + + let is_empty = fs::read_dir(&target_dir) + .map_err(|e| InstallError::ReadDir(e, target_dir.clone()))? + .next() + .is_none(); + + // Consider it installed and skip cloning. + if is_empty { + // Assumes the folder is perfectly empty + let status = Command::new("git") + .arg("clone") + .arg("--depth") + .arg("1") + .arg(git_repo_url) + .arg(&target_dir) + .status() + .map_err(|e| InstallError::GitExecution(e, git_repo_url.clone()))?; + + if !status.success() { + // Force to remove file, if something went wrong + let _ = std::fs::remove_dir_all(&target_dir); + return Err(InstallError::GitCloneFailed(git_repo_url.clone())); + } + installed_repos.push(target_dir.clone()); + } + + let config_path = Config::get_path(&target_dir)?; + let loaded_config = Config::load(config_path)?; + + Self::install_repos(&loaded_config.dependencies, deps_dir, installed_repos)?; + } + + Ok(()) + } +} + +impl Display for InstalledRepos { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "[")?; + for (index, path) in self.0.iter().enumerate() { + if index > 0 { + write!(f, ",")?; + } + write!(f, "\n {}", path.display())?; + } + if !self.0.is_empty() { + writeln!(f)?; + } + write!(f, "]") + } +} diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs index 50cb5fb1..b78fadac 100644 --- a/crates/cli/src/commands/mod.rs +++ b/crates/cli/src/commands/mod.rs @@ -3,6 +3,7 @@ pub mod clean; pub mod core; pub mod error; pub mod init; +pub mod install; pub mod regtest; pub mod test; diff --git a/crates/cli/src/config/core.rs b/crates/cli/src/config/core.rs index 19d6aa39..d9d35023 100644 --- a/crates/cli/src/config/core.rs +++ b/crates/cli/src/config/core.rs @@ -77,7 +77,8 @@ impl Config { } } - config.dependencies.validate().map_err(ConfigError::InvalidDependency) + config.dependencies.validate()?; + Ok(()) } fn validate_network(network: &String) -> Result<(), ConfigError> { diff --git a/crates/cli/src/config/error.rs b/crates/cli/src/config/error.rs index 1105a648..25e2b5a3 100644 --- a/crates/cli/src/config/error.rs +++ b/crates/cli/src/config/error.rs @@ -1,5 +1,7 @@ use std::path::PathBuf; +use smplx_build::error::DependencyValidationError; + #[derive(thiserror::Error, Debug)] pub enum ConfigError { #[error("IO error: {0}")] @@ -8,8 +10,8 @@ pub enum ConfigError { #[error("TOML parse error: {0}")] TomlParse(#[from] toml::de::Error), - #[error("Invalid dependency '{0}': you must specify either a 'path' or a 'git' repository")] - InvalidDependency(String), + #[error(transparent)] + Dependency(#[from] DependencyValidationError), #[error("Network name should either be `Liquid`, `LiquidTestnet` or `ElementsRegtest`, got: {0}")] BadNetworkName(String), diff --git a/crates/sdk/src/program/logger.rs b/crates/sdk/src/program/logger.rs index 1e6fe1a6..1bbe0eba 100644 --- a/crates/sdk/src/program/logger.rs +++ b/crates/sdk/src/program/logger.rs @@ -1,10 +1,9 @@ use std::{cell::RefCell, fmt::Write}; -use simplicityhl::{ - debug::DebugSymbols, - simplicity::node::{Node, Redeem}, - tracker::{DefaultTracker, TrackerLogLevel}, -}; +use simplicityhl::ast::ElementsJetHinter; +use simplicityhl::debug::DebugSymbols; +use simplicityhl::simplicity::node::{Node, Redeem}; +use simplicityhl::tracker::{DefaultTracker, TrackerLogLevel}; thread_local! { pub(super) static PROGRAM_LOGGER: RefCell = const { RefCell::new(ProgramLogger { cost_info: None, trace_buffer: Vec::new() }) }; @@ -40,7 +39,7 @@ impl ProgramLogger { pub fn make_tracker(debug_symbols: &DebugSymbols, log_level: TrackerLogLevel) -> DefaultTracker<'_> { Self::clear_logs(); - let tracker = DefaultTracker::new(debug_symbols); + let tracker = DefaultTracker::build(debug_symbols, Box::new(ElementsJetHinter)); let tracker = if log_level >= TrackerLogLevel::Debug { tracker.with_debug_sink(|label, value| { diff --git a/fixtures/.gitignore b/fixtures/.gitignore index 327555cd..cbd40b05 100644 --- a/fixtures/.gitignore +++ b/fixtures/.gitignore @@ -1 +1 @@ -src/artifacts +src/artifacts \ No newline at end of file diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index 7684466e..333dd1ed 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "simplicityhl" version = "0.6.0-rc.0" -source = "git+https://github.com/LesterEvSe/SimplicityHL.git?branch=feature%2Fdependency-builder#710a6315f4fa2dfcb1c26542a533bc2c3bf0e2b8" +source = "git+https://github.com/BlockstreamResearch/SimplicityHL.git#2cf1f8e5c6af9c34f81e13e2633156657dea9d0e" dependencies = [ "base64 0.21.7", "chumsky", diff --git a/fixtures/Simplex.toml b/fixtures/Simplex.toml index 7a272b7b..e3a768bf 100644 --- a/fixtures/Simplex.toml +++ b/fixtures/Simplex.toml @@ -1,13 +1,15 @@ -# DEFAULT CONFIG +# DEFAULT CONFIG -# [build] -# src_dir = "./simf" +[build] +# src_dir = "./src" # simf_files = ["*.simf"] # out_dir = "./src/artifacts" [dependencies] -merkle = { path = "simf/deps/merkle" } -base_math = { path = "simf/deps/math" } +merkle = { path = "deps/merkle" } +base_math = { path = "deps/math" } + +test_crate = { git = "https://github.com/LesterEvSe/test-simplex-crate" } # [regtest] # mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/Simplex.toml b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/Simplex.toml new file mode 100644 index 00000000..66b8d4ff --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/Simplex.toml @@ -0,0 +1,32 @@ +# DEFAULT CONFIG + +[build] +src_dir = "./examples" +# simf_files = ["*.simf"] +# out_dir = "./src/artifacts" + +# [dependencies] +# merkle = { path = "simf/deps/merkle" } +# base_math = { path = "simf/deps/math" } + +# [regtest] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" +# bitcoins = 10_000_000 +# rpc_port = 18443 +# esplora_port = 3000 +# rpc_user = "user" +# rpc_password = "password" + +# [test] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" +# bitcoins = 10_000_000 +# verbosity = 0 + +# [test.esplora] +# url = "" +# network = "" + +# [test.rpc] +# url = "" +# username = "" +# password = "" diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf new file mode 100644 index 00000000..e671493c --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf @@ -0,0 +1,10 @@ +fn sum(elt: u32, acc: u32) -> u32 { + let (_, acc): (bool, u32) = jet::add_32(elt, acc); + acc +} + +fn main() { + let arr: [u32; 7] = [1, 2, 3, 4, 5, 6, 7]; + let sum: u32 = array_fold::(arr, 0); + assert!(jet::eq_32(sum, 28)); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf new file mode 100644 index 00000000..9d86325c --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf @@ -0,0 +1,12 @@ +// From https://github.com/BlockstreamResearch/SimplicityHL/issues/153 + +fn sum(elt: u32, acc: u32) -> u32 { + let (_, acc): (bool, u32) = jet::add_32(elt, acc); + acc +} + +fn main() { + let arr: [u32; 8] = [1, 1, 1, 1, 1, 1, 1, 1]; + let sum: u32 = array_fold::(arr, 0); + assert!(jet::eq_32(sum, 8)); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf new file mode 100644 index 00000000..f6c5e76c --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf @@ -0,0 +1,8 @@ +fn main() { + let ab: u16 = <(u8, u8)>::into((0x10, 0x01)); + let c: u16 = 0x1001; + assert!(jet::eq_16(ab, c)); + let ab: u8 = <(u4, u4)>::into((0b1011, 0b1101)); + let c: u8 = 0b10111101; + assert!(jet::eq_8(ab, c)); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf new file mode 100644 index 00000000..3eadb44c --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf @@ -0,0 +1,22 @@ +/* + * This program is an emulation of CTV using simplicity + * + * Instead of specifying the template hash as in BIP CTV, + * we require the user to specify all the components of the sighash + * that they want to commit. + */ +fn main() { + let ctx: Ctx8 = jet::sha_256_ctx_8_init(); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_script_sigs_hash()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::num_inputs()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_sequences_hash()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::num_outputs()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::outputs_hash()); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_index()); + let ctv_hash: u256 = jet::sha_256_ctx_8_finalize(ctx); + + let expected_hash: u256 = 0xae3d019b30529c6044d2b3d7ee2e0ee5db51a7f05ed5db8f089cd5d455f1fc5d; + assert!(jet::eq_256(ctv_hash, expected_hash)); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf new file mode 100644 index 00000000..8a11a54a --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf @@ -0,0 +1,62 @@ +/* + * ESCROW WITH DELAY + * + * An escrow agent can approve the movement of coins in cooperation with the + * sender or the recipient. The escrow agent cannot steal the coins for himself. + * The sender can refund her coins after a timeout. + * + * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#escrowwithdelay + */ +fn not(bit: bool) -> bool { + ::into(jet::complement_1(::into(bit))) +} + +fn checksig(pk: Pubkey, sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((pk, msg), sig); +} + +fn checksig_add(counter: u8, pk: Pubkey, maybe_sig: Option) -> u8 { + match maybe_sig { + Some(sig: Signature) => { + checksig(pk, sig); + let (carry, new_counter): (bool, u8) = jet::increment_8(counter); + assert!(not(carry)); + new_counter + } + None => counter, + } +} + +fn check2of3multisig(pks: [Pubkey; 3], maybe_sigs: [Option; 3]) { + let [pk1, pk2, pk3]: [Pubkey; 3] = pks; + let [sig1, sig2, sig3]: [Option; 3] = maybe_sigs; + + let counter1: u8 = checksig_add(0, pk1, sig1); + let counter2: u8 = checksig_add(counter1, pk2, sig2); + let counter3: u8 = checksig_add(counter2, pk3, sig3); + + let threshold: u8 = 2; + assert!(jet::eq_8(counter3, threshold)); +} + +fn transfer_spend(maybe_sigs: [Option; 3]) { + let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G + let recipient_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G + let escrow_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G + check2of3multisig([sender_pk, recipient_pk, escrow_pk], maybe_sigs); +} + +fn timeout_spend(sender_sig: Signature) { + let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G + checksig(sender_pk, sender_sig); + let timeout: Distance = 1000; + jet::broken_do_not_use_check_lock_distance(timeout); +} + +fn main() { + match witness::TRANSFER_OR_TIMEOUT { + Left(maybe_sigs: [Option; 3]) => transfer_spend(maybe_sigs), + Right(sender_sig: Signature) => timeout_spend(sender_sig), + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit new file mode 100644 index 00000000..5b32cede --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit @@ -0,0 +1,6 @@ +{ + "TRANSFER_OR_TIMEOUT": { + "value": "Right(0xedb6865094260f8558728233aae017dd0969a2afe5f08c282e1ab659bf2462684c99a64a2a57246358a0d632671778d016e6df7381293dd5bb9f0999d38640d4)", + "type": "Either<[Option; 3], Signature>" + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf new file mode 100644 index 00000000..f554d894 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf @@ -0,0 +1,32 @@ +// Add counter to streaming hash and finalize when the loop exists +fn hash_counter_8(ctx: Ctx8, unused: (), byte: u8) -> Either { + let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, byte); + match jet::all_8(byte) { + true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), + false => Right(new_ctx), + } +} + +// Add counter to streaming hash and finalize when the loop exists +fn hash_counter_16(ctx: Ctx8, unused: (), bytes: u16) -> Either { + let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_2(ctx, bytes); + match jet::all_16(bytes) { + true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), + false => Right(new_ctx), + } +} + +fn main() { + // Hash bytes 0x00 to 0xff + let ctx: Ctx8 = jet::sha_256_ctx_8_init(); + let out: Either = for_while::(ctx, ()); + let expected: u256 = 0x40aff2e9d2d8922e47afd4648e6967497158785fbd1da870e7110266bf944880; + assert!(jet::eq_256(expected, unwrap_left::(out))); + + // Hash bytes 0x0000 to 0xffff + // This takes ~10 seconds on my computer + // let ctx: Ctx8 = jet::sha_256_ctx_8_init(); + // let out: Either = for_while::(ctx, ()); + // let expected: u256 = 0x281f79f89f0121c31db2bea5d7151db246349b25f5901c114505c18bfaa50ba1; + // assert!(jet::eq_256(expected, unwrap_left::(out))); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf new file mode 100644 index 00000000..fdc29ae5 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf @@ -0,0 +1,42 @@ +/* + * HODL VAULT + * + * Lock your coins until the Bitcoin price exceeds a threshold. + * + * An oracle signs a message with the current block height and the current + * Bitcoin price. The block height is compared with a minimum height to prevent + * the use of old data. The transaction is timelocked to the oracle height, + * which means that the transaction becomes valid after the oracle height. + */ +fn checksig(pk: Pubkey, sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((pk, msg), sig); +} + +fn checksigfromstack(pk: Pubkey, bytes: [u32; 2], sig: Signature) { + let [word1, word2]: [u32; 2] = bytes; + let hasher: Ctx8 = jet::sha_256_ctx_8_init(); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word1); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word2); + let msg: u256 = jet::sha_256_ctx_8_finalize(hasher); + jet::bip_0340_verify((pk, msg), sig); +} + +fn main() { + let min_height: Height = 1000; + let oracle_height: Height = witness::ORACLE_HEIGHT; + assert!(jet::le_32(min_height, oracle_height)); + jet::check_lock_height(oracle_height); + + let target_price: u32 = 100000; // laser eyes until 100k + let oracle_price: u32 = witness::ORACLE_PRICE; + assert!(jet::le_32(target_price, oracle_price)); + + let oracle_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G + let oracle_sig: Signature = witness::ORACLE_SIG; + checksigfromstack(oracle_pk, [oracle_height, oracle_price], oracle_sig); + + let owner_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G + let owner_sig: Signature = witness::OWNER_SIG; + checksig(owner_pk, owner_sig); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit new file mode 100644 index 00000000..56075205 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit @@ -0,0 +1,18 @@ +{ + "ORACLE_HEIGHT": { + "value": "1000", + "type": "u32" + }, + "ORACLE_PRICE": { + "value": "100000", + "type": "u32" + }, + "ORACLE_SIG": { + "value": "0x90231b8de96a1f940ddcf406fe8389417ca8fb0b03151608e2f94b31b443a7e0d26a12e437df69028f09027c37d5f6742a10c1e8864061d119b8bbce962d26d3", + "type": "Signature" + }, + "OWNER_SIG": { + "value": "0xf2341f571f069216edfc72822f6094b8ec339c2f72dc64aea0eed1e3d60abf4572fdd04618e5b5bc672ccd71cfaf125b6c1b101aeca3a7b938fe83932ab38743", + "type": "Signature" + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit new file mode 100644 index 00000000..85efd29f --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit @@ -0,0 +1,6 @@ +{ + "COMPLETE_OR_CANCEL": { + "value": "Left((0x0000000000000000000000000000000000000000000000000000000000000000, 0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f))", + "type": "Either<(u256, Signature), Signature>" + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf new file mode 100644 index 00000000..2ee98c5e --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf @@ -0,0 +1,46 @@ +/* + * HTLC (Hash Time-Locked Contract) + * + * The recipient can spend the coins by providing the secret preimage of a hash. + * The sender can cancel the transfer after a fixed block height. + * + * HTLCs enable two-way payment channels and multi-hop payments, + * such as on the Lightning network. + * + * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#htlc + */ +fn sha2(string: u256) -> u256 { + let hasher: Ctx8 = jet::sha_256_ctx_8_init(); + let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); + jet::sha_256_ctx_8_finalize(hasher) +} + +fn checksig(pk: Pubkey, sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((pk, msg), sig); +} + +fn complete_spend(preimage: u256, recipient_sig: Signature) { + let hash: u256 = sha2(preimage); + let expected_hash: u256 = 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925; // sha2([0x00; 32]) + assert!(jet::eq_256(hash, expected_hash)); + let recipient_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G + checksig(recipient_pk, recipient_sig); +} + +fn cancel_spend(sender_sig: Signature) { + let timeout: Height = 1000; + jet::check_lock_height(timeout); + let sender_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G + checksig(sender_pk, sender_sig) +} + +fn main() { + match witness::COMPLETE_OR_CANCEL { + Left(preimage_sig: (u256, Signature)) => { + let (preimage, recipient_sig): (u256, Signature) = preimage_sig; + complete_spend(preimage, recipient_sig); + }, + Right(sender_sig: Signature) => cancel_spend(sender_sig), + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit new file mode 100644 index 00000000..16752030 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit @@ -0,0 +1,6 @@ +{ + "INHERIT_OR_NOT": { + "value": "Left(0x755201bb62b0a8b8d18fd12fc02951ea3998ba42bfc6664daaf8a0d2298cad43cdc21358c7c82f37654275dc2fea8c858adbe97bac92828b498a5a237004db6f)", + "type": "Either>" + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf new file mode 100644 index 00000000..9790a1cf --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf @@ -0,0 +1,51 @@ +/* + * LAST WILL + * + * The inheritor can spend the coins if the owner doesn't move the them for 180 + * days. The owner has to repeat the covenant when he moves the coins with his + * hot key. The owner can break out of the covenant with his cold key. + */ +fn checksig(pk: Pubkey, sig: Signature) { + let msg: u256 = jet::sig_all_hash(); + jet::bip_0340_verify((pk, msg), sig); +} + +// Enforce the covenant to repeat in the first output. +// +// Elements has explicit fee outputs, so enforce a fee output in the second output. +// Disallow further outputs. +fn recursive_covenant() { + assert!(jet::eq_32(jet::num_outputs(), 2)); + let this_script_hash: u256 = jet::current_script_hash(); + let output_script_hash: u256 = unwrap(jet::output_script_hash(0)); + assert!(jet::eq_256(this_script_hash, output_script_hash)); + assert!(unwrap(jet::output_is_fee(1))); +} + +fn inherit_spend(inheritor_sig: Signature) { + let days_180: Distance = 25920; + jet::broken_do_not_use_check_lock_distance(days_180); + let inheritor_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G + checksig(inheritor_pk, inheritor_sig); +} + +fn cold_spend(cold_sig: Signature) { + let cold_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G + checksig(cold_pk, cold_sig); +} + +fn refresh_spend(hot_sig: Signature) { + let hot_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G + checksig(hot_pk, hot_sig); + recursive_covenant(); +} + +fn main() { + match witness::INHERIT_OR_NOT { + Left(inheritor_sig: Signature) => inherit_spend(inheritor_sig), + Right(cold_or_hot: Either) => match cold_or_hot { + Left(cold_sig: Signature) => cold_spend(cold_sig), + Right(hot_sig: Signature) => refresh_spend(hot_sig), + }, + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf new file mode 100644 index 00000000..c4df525f --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf @@ -0,0 +1,5 @@ +use crate::math::add; + +fn main() { + assert!(jet::eq_32(add(2, 2), 4)); +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf new file mode 100644 index 00000000..25076863 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf @@ -0,0 +1,4 @@ +pub fn add(a: u32, b: u32) -> u32 { + let (_, sum): (bool, u32) = jet::add_32(a, b); + sum +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf new file mode 100644 index 00000000..70979db9 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf @@ -0,0 +1,26 @@ +mod math { + pub mod ops { + pub fn double(x: u32) -> u32 { + let (_, res): (bool, u32) = jet::add_32(x, x); + res + } + } +} + +mod business_logic { + use crate::math::ops::double; + + pub fn calculate_fee(base_price: u32, tax: u32) -> u32 { + let (_, res): (bool, u32) = jet::add_32(double(base_price), tax); + res + } +} + +use crate::business_logic::calculate_fee; +fn main() { + let price: u32 = 15; + let tax: u32 = 5; + + let total: u32 = calculate_fee(price, tax); + assert!(jet::eq_32(total, 35)); +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf new file mode 100644 index 00000000..cd934f4d --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf @@ -0,0 +1,34 @@ +mod unit_2 { + pub fn hash(x: u32, y: u32) -> u32 { + jet::xor_32(x, y) + } +} + +mod unit_1 { + use crate::unit_2::hash as temp_hash; + pub fn get_root(tx1: u32, tx2: u32) -> u32 { + temp_hash(tx1, tx2) + } + pub fn hash(tx1: u32, tx2: u32) -> u32 { + jet::and_32(tx1, tx2) + } +} + +// main unit +mod unit_0 { + use crate::unit_1::{get_root, hash as and_hash}; + use crate::unit_2::hash as or_hash; + + pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { + let root: u32 = get_root(tx1, tx2); + or_hash(prev_hash, root) + } + + fn main() { + let block_val_hash: u32 = get_block_value_hash(5, 10, 20); + assert!(jet::eq_32(block_val_hash, 27)); + let first_value: u32 = 15; + let second_value: u32 = 22; + assert!(jet::eq_32(and_hash(first_value, second_value), 6)); + } +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf new file mode 100644 index 00000000..cead0852 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf @@ -0,0 +1,16 @@ +use merkle::build_root::{get_root, hash as and_hash}; +use base_math::simple_op::hash as or_hash; + +pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { + let root: u32 = get_root(tx1, tx2); + or_hash(prev_hash, root) +} + +fn main() { + let block_val_hash: u32 = get_block_value_hash(5, 10, 20); + assert!(jet::eq_32(block_val_hash, 27)); + + let first_value: u32 = 15; + let second_value: u32 = 22; + assert!(jet::eq_32(and_hash(first_value, second_value), 6)); +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/math/simple_op.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/math/simple_op.simf new file mode 100644 index 00000000..b152a361 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/math/simple_op.simf @@ -0,0 +1,3 @@ +pub fn hash(x: u32, y: u32) -> u32 { + jet::xor_32(x, y) +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf new file mode 100644 index 00000000..f41c37e4 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf @@ -0,0 +1,9 @@ +use math::simple_op::hash as temp_hash; + +pub fn get_root(tx1: u32, tx2: u32) -> u32 { + temp_hash(tx1, tx2) +} + +pub fn hash(tx1: u32, tx2: u32) -> u32 { + jet::and_32(tx1, tx2) +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf new file mode 100644 index 00000000..0e463d89 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf @@ -0,0 +1,5 @@ +pub fn sha256(data: u32) -> u256 { + let ctx: Ctx8 = jet::sha_256_ctx_8_init(); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data); + jet::sha_256_ctx_8_finalize(ctx) +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf new file mode 100644 index 00000000..8f5e77f2 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf @@ -0,0 +1,24 @@ +mod unit_1 { + pub fn add(a: u32, b: u32) -> u32 { + let (_, res): (bool, u32) = jet::add_32(a, b); + res + } +} + +mod unit_2 { + pub fn sha256(data: u32) -> u256 { + let ctx: Ctx8 = jet::sha_256_ctx_8_init(); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data); + jet::sha_256_ctx_8_finalize(ctx) + } +} + +mod unit_0 { + use crate::unit_1::add; + use crate::unit_2::sha256; + + fn main() { + let sum: u32 = add(2, 3); + let hash: u256 = sha256(sum); + } +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf new file mode 100644 index 00000000..81031d4c --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf @@ -0,0 +1,7 @@ +use math::arithmetic::add; +use crypto::hashes::sha256; + +fn main() { + let sum: u32 = add(2, 3); + let hash: u256 = sha256(sum); +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf new file mode 100644 index 00000000..2f348e0c --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf @@ -0,0 +1,4 @@ +pub fn add(a: u32, b: u32) -> u32 { + let (_, res): (bool, u32) = jet::add_32(a, b); + res +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf new file mode 100644 index 00000000..ef182924 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf @@ -0,0 +1,29 @@ +mod unit_2 { + pub type Smth = u32; + + pub fn get_five() -> u32 { + 5 + } +} + +mod unit_1 { + pub use crate::unit_2::Smth; + + pub fn two() -> Smth { + 2 + } +} + +mod unit_0 { + pub use crate::unit_1::two as smth; + use crate::unit_2::{get_five, Smth}; + + fn seven() -> u32 { + 7 + } + + fn main() { + let (_, temp): (bool, u32) = jet::add_32(smth(), get_five()); + assert!(jet::eq_32(temp, seven())); + } +} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf new file mode 100644 index 00000000..7897ab8a --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf @@ -0,0 +1,11 @@ +pub use temp::constants::utils::two as smth; +use temp::funcs::{get_five, Smth}; + +fn seven() -> u32 { + 7 +} + +fn main() { + let (_, temp): (bool, u32) = jet::add_32(smth(), get_five()); + assert!(jet::eq_32(temp, seven())); +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf new file mode 100644 index 00000000..4fd2102e --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf @@ -0,0 +1,5 @@ +pub use crate::funcs::Smth; + +pub fn two() -> Smth { + 2 +} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf new file mode 100644 index 00000000..0ff2da55 --- /dev/null +++ b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf @@ -0,0 +1,5 @@ +pub type Smth = u32; + +pub fn get_five() -> u32 { + 5 +} \ No newline at end of file diff --git a/fixtures/simf/deps/math/Simplex.toml b/fixtures/deps/math/Simplex.toml similarity index 100% rename from fixtures/simf/deps/math/Simplex.toml rename to fixtures/deps/math/Simplex.toml diff --git a/fixtures/simf/deps/math/simf/simple_op.simf b/fixtures/deps/math/simf/simple_op.simf similarity index 100% rename from fixtures/simf/deps/math/simf/simple_op.simf rename to fixtures/deps/math/simf/simple_op.simf diff --git a/fixtures/simf/deps/merkle/Simplex.toml b/fixtures/deps/merkle/Simplex.toml similarity index 98% rename from fixtures/simf/deps/merkle/Simplex.toml rename to fixtures/deps/merkle/Simplex.toml index dcd08ec7..43b44971 100644 --- a/fixtures/simf/deps/merkle/Simplex.toml +++ b/fixtures/deps/merkle/Simplex.toml @@ -1,6 +1,6 @@ # DEFAULT CONFIG -#[build] +# [build] # src_dir = "./simf" # simf_files = ["*.simf"] # out_dir = "./src/artifacts" diff --git a/fixtures/deps/merkle/simf/build_root.simf b/fixtures/deps/merkle/simf/build_root.simf new file mode 100644 index 00000000..867ee40f --- /dev/null +++ b/fixtures/deps/merkle/simf/build_root.simf @@ -0,0 +1,14 @@ +// Wrapper module required for proper test suite integration. +pub mod wrapper { + use math::simple_op::hash as temp_hash; + + pub fn get_root(tx1: u32, tx2: u32) -> u32 { + temp_hash(tx1, tx2) + } + + pub fn hash(tx1: u32, tx2: u32) -> u32 { + jet::and_32(tx1, tx2) + } +} + +pub use crate::wrapper::{get_root, hash}; diff --git a/fixtures/deps/test-simplex-crate-3a0ece279ca58451/Simplex.toml b/fixtures/deps/test-simplex-crate-3a0ece279ca58451/Simplex.toml new file mode 100644 index 00000000..2955815b --- /dev/null +++ b/fixtures/deps/test-simplex-crate-3a0ece279ca58451/Simplex.toml @@ -0,0 +1,30 @@ +# DEFAULT CONFIG + +[build] +src_dir = "test" +# src_dir = "./simf" +# simf_files = ["*.simf"] +# out_dir = "./src/artifacts" + +[dependencies] +# std = { git = "BlockstreamResearch/simf-std@v0.0.1", path = "lib/simf-std/contracts" } +hl_compiler = { git = "https://github.com/LesterEvSe/SimplicityHL" } + +# ARGS +# std=lib/simf-std/contracts +# std:math=node_modules/simf-math/contracts + +# [regtest] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" + +# [test] +# mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" + +# [test.esplora] +# url = "" +# network = "" + +# [test.rpc] +# url = "" +# username = "" +# password = "" diff --git a/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf b/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf new file mode 100644 index 00000000..214fc97d --- /dev/null +++ b/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf @@ -0,0 +1 @@ +pub use hl_compiler::multiple_deps::math::simple_op::hash; \ No newline at end of file diff --git a/fixtures/simf/deps/merkle/simf/build_root.simf b/fixtures/simf/deps/merkle/simf/build_root.simf deleted file mode 100644 index 4075d31e..00000000 --- a/fixtures/simf/deps/merkle/simf/build_root.simf +++ /dev/null @@ -1,13 +0,0 @@ -use math::simple_op::hash as temp_hash; - -//pub mod wrapper { - pub fn get_root(tx1: u32, tx2: u32) -> u32 { - temp_hash(tx1, tx2) - } - - pub fn hash(tx1: u32, tx2: u32) -> u32 { - jet::and_32(tx1, tx2) - } -//} - -//pub use crate::wrapper::{get_root, hash}; diff --git a/fixtures/simf/imports/multidep.simf b/fixtures/simf/imports/multidep.simf index b13d74c5..b48c90f6 100644 --- a/fixtures/simf/imports/multidep.simf +++ b/fixtures/simf/imports/multidep.simf @@ -1,5 +1,5 @@ use merkle::build_root::{get_root, hash as and_hash}; -use base_math::simple_op::hash as or_hash; +use test_crate::smth::hash as or_hash; pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { let root: u32 = get_root(tx1, tx2); diff --git a/fixtures/simf/p2pk.simf b/fixtures/simf/p2pk.simf index f6a75e67..db4f27c7 100644 --- a/fixtures/simf/p2pk.simf +++ b/fixtures/simf/p2pk.simf @@ -1,3 +1,3 @@ fn main() { jet::bip_0340_verify((param::PUBLIC_KEY, jet::sig_all_hash()), witness::SIGNATURE) -} +} \ No newline at end of file From fcd50c9e4868e6b471fdcad28efb80491b40d01e Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 16 Jun 2026 14:44:40 +0300 Subject: [PATCH 05/10] fix: `ArtifactsResolver::contains_main` function, refactor docstring --- Cargo.lock | 13 ------------- crates/build/Cargo.toml | 1 - crates/build/src/generator.rs | 6 ++++++ crates/build/src/resolver.rs | 25 +++++++++++++++++-------- fixtures/Cargo.lock | 13 ------------- 5 files changed, 23 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1efd224..c976a8d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,18 +1005,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.14", - "regex-syntax 0.8.10", -] - [[package]] name = "regex-automata" version = "0.3.9" @@ -1317,7 +1305,6 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "regex", "serde", "simplicityhl", "syn", diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index fc389b1a..8487fbfe 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -14,7 +14,6 @@ toml = { workspace = true } serde = { workspace = true } simplicityhl = { workspace = true } -regex = { version = "1" } 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" } diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index fcab7f1b..2a6d4601 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -347,6 +347,12 @@ impl ArtifactsGenerator { /// Converts "https://github.com/smplx/core.git" /// into a Cargo-style path: "core-a1b2c3d4e5f67890" + /// + /// # Returns + /// + /// - `Some(PathBuf)` when a repository name can be extracted from the URL. + /// - `None` when the URL is empty or malformed such that no repository name + /// can be determined. pub fn generate_hashed_repo_path(url: &str) -> Option { let clean_url = url.strip_suffix(".git").unwrap_or(url); let repo_name = clean_url.split('/').next_back()?; diff --git a/crates/build/src/resolver.rs b/crates/build/src/resolver.rs index 8a6995a2..6398f667 100644 --- a/crates/build/src/resolver.rs +++ b/crates/build/src/resolver.rs @@ -1,13 +1,13 @@ use std::collections::HashSet; use std::path::{Path, PathBuf}; -use std::sync::OnceLock; use std::{env, fs}; use globwalk::FileType; -use regex::Regex; +use simplicityhl::parse::{self, ParseFromStr}; use simplicityhl::resolution::{DependencyMapBuilder, ValidatedDeps}; use simplicityhl::source::CanonPath; +use simplicityhl::str::FunctionName; use crate::config::{DEFAULT_DEPENDENCY_DIR, Dependency}; use crate::{ArtifactsGenerator, BuildConfig, DependencyConfig}; @@ -110,14 +110,23 @@ impl ArtifactsResolver { .map_err(|e| BuildError::DependencyMap(e.to_string())) } - /// Checks whether the source contains a `fn main(...)` declaration, - /// regardless of visibility (`pub fn main` or `fn main`) or nesting - /// inside `mod { ... }` blocks; The regex matches anywhere in the text. + /// Checks whether the source declares a `fn main(...)`, + /// finding it even when nested inside `mod { ... }` blocks. fn contains_main(source: &str) -> bool { - static RE: OnceLock = OnceLock::new(); - let re = RE.get_or_init(|| Regex::new(r"(?m)^\s*(pub\s+)?fn\s+main\s*\(").unwrap()); + let Ok(parsed_program) = parse::Program::parse_from_str(source) else { + return false; + }; + Self::rec_main_checker(parsed_program.items(), &FunctionName::main()) + } - re.is_match(source) + /// Recursively searches `items` (descending into nested modules) for a + /// function named `main`. + fn rec_main_checker(items: &[parse::Item], main_name: &FunctionName) -> bool { + items.iter().any(|item| match item { + parse::Item::Function(func) => func.name() == main_name, + parse::Item::Module(module) => Self::rec_main_checker(module.items(), main_name), + _ => false, + }) } } diff --git a/fixtures/Cargo.lock b/fixtures/Cargo.lock index 333dd1ed..7687ad6e 100644 --- a/fixtures/Cargo.lock +++ b/fixtures/Cargo.lock @@ -921,18 +921,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.14", - "regex-syntax 0.8.10", -] - [[package]] name = "regex-automata" version = "0.3.9" @@ -1241,7 +1229,6 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "regex", "serde", "simplicityhl", "syn", From 5a7654de64a50679acb648a7ca37a0703893dd7b Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 16 Jun 2026 14:46:18 +0300 Subject: [PATCH 06/10] chore: format code with `cargo fmt` --- crates/build/src/generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index 2a6d4601..d6925c09 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -347,7 +347,7 @@ impl ArtifactsGenerator { /// Converts "https://github.com/smplx/core.git" /// into a Cargo-style path: "core-a1b2c3d4e5f67890" - /// + /// /// # Returns /// /// - `Some(PathBuf)` when a repository name can be extracted from the URL. From b8c2d960912905379c4a820829f41b19df521e27 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 16 Jun 2026 16:59:45 +0300 Subject: [PATCH 07/10] refactor: delete redundant files in `fixtures` --- .../examples/array_fold.simf | 10 --- .../examples/array_fold_2n.simf | 12 ---- .../examples/cat.simf | 8 --- .../examples/ctv.simf | 22 ------- .../examples/escrow_with_delay.simf | 62 ------------------- .../examples/escrow_with_delay.timeout.wit | 6 -- .../examples/hash_loop.simf | 32 ---------- .../examples/hodl_vault.simf | 42 ------------- .../examples/hodl_vault.wit | 18 ------ .../examples/htlc.complete.wit | 6 -- .../examples/htlc.simf | 46 -------------- .../examples/last_will.inherit.wit | 6 -- .../examples/last_will.simf | 51 --------------- .../examples/local_crate/main.simf | 5 -- .../examples/local_crate/math.simf | 4 -- .../examples/modules.simf | 26 -------- .../examples/multiple_deps/flattened.simf | 34 ---------- .../examples/multiple_deps/main.simf | 16 ----- .../multiple_deps/merkle/build_root.simf | 9 --- .../simple_multidep/crypto/hashes.simf | 5 -- .../examples/simple_multidep/flattened.simf | 24 ------- .../examples/simple_multidep/main.simf | 7 --- .../simple_multidep/math/arithmetic.simf | 4 -- .../{multiple_deps/math => }/simple_op.simf | 0 .../examples/single_dep/flattened.simf | 29 --------- .../examples/single_dep/main.simf | 11 ---- .../single_dep/temp/constants/utils.simf | 5 -- .../examples/single_dep/temp/funcs.simf | 5 -- .../test/smth.simf | 2 +- 29 files changed, 1 insertion(+), 506 deletions(-) delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf rename fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/{multiple_deps/math => }/simple_op.simf (100%) delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf delete mode 100644 fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf deleted file mode 100644 index e671493c..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold.simf +++ /dev/null @@ -1,10 +0,0 @@ -fn sum(elt: u32, acc: u32) -> u32 { - let (_, acc): (bool, u32) = jet::add_32(elt, acc); - acc -} - -fn main() { - let arr: [u32; 7] = [1, 2, 3, 4, 5, 6, 7]; - let sum: u32 = array_fold::(arr, 0); - assert!(jet::eq_32(sum, 28)); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf deleted file mode 100644 index 9d86325c..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/array_fold_2n.simf +++ /dev/null @@ -1,12 +0,0 @@ -// From https://github.com/BlockstreamResearch/SimplicityHL/issues/153 - -fn sum(elt: u32, acc: u32) -> u32 { - let (_, acc): (bool, u32) = jet::add_32(elt, acc); - acc -} - -fn main() { - let arr: [u32; 8] = [1, 1, 1, 1, 1, 1, 1, 1]; - let sum: u32 = array_fold::(arr, 0); - assert!(jet::eq_32(sum, 8)); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf deleted file mode 100644 index f6c5e76c..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/cat.simf +++ /dev/null @@ -1,8 +0,0 @@ -fn main() { - let ab: u16 = <(u8, u8)>::into((0x10, 0x01)); - let c: u16 = 0x1001; - assert!(jet::eq_16(ab, c)); - let ab: u8 = <(u4, u4)>::into((0b1011, 0b1101)); - let c: u8 = 0b10111101; - assert!(jet::eq_8(ab, c)); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf deleted file mode 100644 index 3eadb44c..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/ctv.simf +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This program is an emulation of CTV using simplicity - * - * Instead of specifying the template hash as in BIP CTV, - * we require the user to specify all the components of the sighash - * that they want to commit. - */ -fn main() { - let ctx: Ctx8 = jet::sha_256_ctx_8_init(); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_script_sigs_hash()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::num_inputs()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_sequences_hash()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::num_outputs()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::outputs_hash()); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_index()); - let ctv_hash: u256 = jet::sha_256_ctx_8_finalize(ctx); - - let expected_hash: u256 = 0xae3d019b30529c6044d2b3d7ee2e0ee5db51a7f05ed5db8f089cd5d455f1fc5d; - assert!(jet::eq_256(ctv_hash, expected_hash)); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf deleted file mode 100644 index 8a11a54a..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.simf +++ /dev/null @@ -1,62 +0,0 @@ -/* - * ESCROW WITH DELAY - * - * An escrow agent can approve the movement of coins in cooperation with the - * sender or the recipient. The escrow agent cannot steal the coins for himself. - * The sender can refund her coins after a timeout. - * - * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#escrowwithdelay - */ -fn not(bit: bool) -> bool { - ::into(jet::complement_1(::into(bit))) -} - -fn checksig(pk: Pubkey, sig: Signature) { - let msg: u256 = jet::sig_all_hash(); - jet::bip_0340_verify((pk, msg), sig); -} - -fn checksig_add(counter: u8, pk: Pubkey, maybe_sig: Option) -> u8 { - match maybe_sig { - Some(sig: Signature) => { - checksig(pk, sig); - let (carry, new_counter): (bool, u8) = jet::increment_8(counter); - assert!(not(carry)); - new_counter - } - None => counter, - } -} - -fn check2of3multisig(pks: [Pubkey; 3], maybe_sigs: [Option; 3]) { - let [pk1, pk2, pk3]: [Pubkey; 3] = pks; - let [sig1, sig2, sig3]: [Option; 3] = maybe_sigs; - - let counter1: u8 = checksig_add(0, pk1, sig1); - let counter2: u8 = checksig_add(counter1, pk2, sig2); - let counter3: u8 = checksig_add(counter2, pk3, sig3); - - let threshold: u8 = 2; - assert!(jet::eq_8(counter3, threshold)); -} - -fn transfer_spend(maybe_sigs: [Option; 3]) { - let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G - let recipient_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G - let escrow_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G - check2of3multisig([sender_pk, recipient_pk, escrow_pk], maybe_sigs); -} - -fn timeout_spend(sender_sig: Signature) { - let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G - checksig(sender_pk, sender_sig); - let timeout: Distance = 1000; - jet::broken_do_not_use_check_lock_distance(timeout); -} - -fn main() { - match witness::TRANSFER_OR_TIMEOUT { - Left(maybe_sigs: [Option; 3]) => transfer_spend(maybe_sigs), - Right(sender_sig: Signature) => timeout_spend(sender_sig), - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit deleted file mode 100644 index 5b32cede..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/escrow_with_delay.timeout.wit +++ /dev/null @@ -1,6 +0,0 @@ -{ - "TRANSFER_OR_TIMEOUT": { - "value": "Right(0xedb6865094260f8558728233aae017dd0969a2afe5f08c282e1ab659bf2462684c99a64a2a57246358a0d632671778d016e6df7381293dd5bb9f0999d38640d4)", - "type": "Either<[Option; 3], Signature>" - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf deleted file mode 100644 index f554d894..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hash_loop.simf +++ /dev/null @@ -1,32 +0,0 @@ -// Add counter to streaming hash and finalize when the loop exists -fn hash_counter_8(ctx: Ctx8, unused: (), byte: u8) -> Either { - let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, byte); - match jet::all_8(byte) { - true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), - false => Right(new_ctx), - } -} - -// Add counter to streaming hash and finalize when the loop exists -fn hash_counter_16(ctx: Ctx8, unused: (), bytes: u16) -> Either { - let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_2(ctx, bytes); - match jet::all_16(bytes) { - true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), - false => Right(new_ctx), - } -} - -fn main() { - // Hash bytes 0x00 to 0xff - let ctx: Ctx8 = jet::sha_256_ctx_8_init(); - let out: Either = for_while::(ctx, ()); - let expected: u256 = 0x40aff2e9d2d8922e47afd4648e6967497158785fbd1da870e7110266bf944880; - assert!(jet::eq_256(expected, unwrap_left::(out))); - - // Hash bytes 0x0000 to 0xffff - // This takes ~10 seconds on my computer - // let ctx: Ctx8 = jet::sha_256_ctx_8_init(); - // let out: Either = for_while::(ctx, ()); - // let expected: u256 = 0x281f79f89f0121c31db2bea5d7151db246349b25f5901c114505c18bfaa50ba1; - // assert!(jet::eq_256(expected, unwrap_left::(out))); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf deleted file mode 100644 index fdc29ae5..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.simf +++ /dev/null @@ -1,42 +0,0 @@ -/* - * HODL VAULT - * - * Lock your coins until the Bitcoin price exceeds a threshold. - * - * An oracle signs a message with the current block height and the current - * Bitcoin price. The block height is compared with a minimum height to prevent - * the use of old data. The transaction is timelocked to the oracle height, - * which means that the transaction becomes valid after the oracle height. - */ -fn checksig(pk: Pubkey, sig: Signature) { - let msg: u256 = jet::sig_all_hash(); - jet::bip_0340_verify((pk, msg), sig); -} - -fn checksigfromstack(pk: Pubkey, bytes: [u32; 2], sig: Signature) { - let [word1, word2]: [u32; 2] = bytes; - let hasher: Ctx8 = jet::sha_256_ctx_8_init(); - let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word1); - let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word2); - let msg: u256 = jet::sha_256_ctx_8_finalize(hasher); - jet::bip_0340_verify((pk, msg), sig); -} - -fn main() { - let min_height: Height = 1000; - let oracle_height: Height = witness::ORACLE_HEIGHT; - assert!(jet::le_32(min_height, oracle_height)); - jet::check_lock_height(oracle_height); - - let target_price: u32 = 100000; // laser eyes until 100k - let oracle_price: u32 = witness::ORACLE_PRICE; - assert!(jet::le_32(target_price, oracle_price)); - - let oracle_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G - let oracle_sig: Signature = witness::ORACLE_SIG; - checksigfromstack(oracle_pk, [oracle_height, oracle_price], oracle_sig); - - let owner_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G - let owner_sig: Signature = witness::OWNER_SIG; - checksig(owner_pk, owner_sig); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit deleted file mode 100644 index 56075205..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/hodl_vault.wit +++ /dev/null @@ -1,18 +0,0 @@ -{ - "ORACLE_HEIGHT": { - "value": "1000", - "type": "u32" - }, - "ORACLE_PRICE": { - "value": "100000", - "type": "u32" - }, - "ORACLE_SIG": { - "value": "0x90231b8de96a1f940ddcf406fe8389417ca8fb0b03151608e2f94b31b443a7e0d26a12e437df69028f09027c37d5f6742a10c1e8864061d119b8bbce962d26d3", - "type": "Signature" - }, - "OWNER_SIG": { - "value": "0xf2341f571f069216edfc72822f6094b8ec339c2f72dc64aea0eed1e3d60abf4572fdd04618e5b5bc672ccd71cfaf125b6c1b101aeca3a7b938fe83932ab38743", - "type": "Signature" - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit deleted file mode 100644 index 85efd29f..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.complete.wit +++ /dev/null @@ -1,6 +0,0 @@ -{ - "COMPLETE_OR_CANCEL": { - "value": "Left((0x0000000000000000000000000000000000000000000000000000000000000000, 0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f))", - "type": "Either<(u256, Signature), Signature>" - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf deleted file mode 100644 index 2ee98c5e..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/htlc.simf +++ /dev/null @@ -1,46 +0,0 @@ -/* - * HTLC (Hash Time-Locked Contract) - * - * The recipient can spend the coins by providing the secret preimage of a hash. - * The sender can cancel the transfer after a fixed block height. - * - * HTLCs enable two-way payment channels and multi-hop payments, - * such as on the Lightning network. - * - * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#htlc - */ -fn sha2(string: u256) -> u256 { - let hasher: Ctx8 = jet::sha_256_ctx_8_init(); - let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); - jet::sha_256_ctx_8_finalize(hasher) -} - -fn checksig(pk: Pubkey, sig: Signature) { - let msg: u256 = jet::sig_all_hash(); - jet::bip_0340_verify((pk, msg), sig); -} - -fn complete_spend(preimage: u256, recipient_sig: Signature) { - let hash: u256 = sha2(preimage); - let expected_hash: u256 = 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925; // sha2([0x00; 32]) - assert!(jet::eq_256(hash, expected_hash)); - let recipient_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G - checksig(recipient_pk, recipient_sig); -} - -fn cancel_spend(sender_sig: Signature) { - let timeout: Height = 1000; - jet::check_lock_height(timeout); - let sender_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G - checksig(sender_pk, sender_sig) -} - -fn main() { - match witness::COMPLETE_OR_CANCEL { - Left(preimage_sig: (u256, Signature)) => { - let (preimage, recipient_sig): (u256, Signature) = preimage_sig; - complete_spend(preimage, recipient_sig); - }, - Right(sender_sig: Signature) => cancel_spend(sender_sig), - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit deleted file mode 100644 index 16752030..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.inherit.wit +++ /dev/null @@ -1,6 +0,0 @@ -{ - "INHERIT_OR_NOT": { - "value": "Left(0x755201bb62b0a8b8d18fd12fc02951ea3998ba42bfc6664daaf8a0d2298cad43cdc21358c7c82f37654275dc2fea8c858adbe97bac92828b498a5a237004db6f)", - "type": "Either>" - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf deleted file mode 100644 index 9790a1cf..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/last_will.simf +++ /dev/null @@ -1,51 +0,0 @@ -/* - * LAST WILL - * - * The inheritor can spend the coins if the owner doesn't move the them for 180 - * days. The owner has to repeat the covenant when he moves the coins with his - * hot key. The owner can break out of the covenant with his cold key. - */ -fn checksig(pk: Pubkey, sig: Signature) { - let msg: u256 = jet::sig_all_hash(); - jet::bip_0340_verify((pk, msg), sig); -} - -// Enforce the covenant to repeat in the first output. -// -// Elements has explicit fee outputs, so enforce a fee output in the second output. -// Disallow further outputs. -fn recursive_covenant() { - assert!(jet::eq_32(jet::num_outputs(), 2)); - let this_script_hash: u256 = jet::current_script_hash(); - let output_script_hash: u256 = unwrap(jet::output_script_hash(0)); - assert!(jet::eq_256(this_script_hash, output_script_hash)); - assert!(unwrap(jet::output_is_fee(1))); -} - -fn inherit_spend(inheritor_sig: Signature) { - let days_180: Distance = 25920; - jet::broken_do_not_use_check_lock_distance(days_180); - let inheritor_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G - checksig(inheritor_pk, inheritor_sig); -} - -fn cold_spend(cold_sig: Signature) { - let cold_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G - checksig(cold_pk, cold_sig); -} - -fn refresh_spend(hot_sig: Signature) { - let hot_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G - checksig(hot_pk, hot_sig); - recursive_covenant(); -} - -fn main() { - match witness::INHERIT_OR_NOT { - Left(inheritor_sig: Signature) => inherit_spend(inheritor_sig), - Right(cold_or_hot: Either) => match cold_or_hot { - Left(cold_sig: Signature) => cold_spend(cold_sig), - Right(hot_sig: Signature) => refresh_spend(hot_sig), - }, - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf deleted file mode 100644 index c4df525f..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/main.simf +++ /dev/null @@ -1,5 +0,0 @@ -use crate::math::add; - -fn main() { - assert!(jet::eq_32(add(2, 2), 4)); -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf deleted file mode 100644 index 25076863..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/local_crate/math.simf +++ /dev/null @@ -1,4 +0,0 @@ -pub fn add(a: u32, b: u32) -> u32 { - let (_, sum): (bool, u32) = jet::add_32(a, b); - sum -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf deleted file mode 100644 index 70979db9..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/modules.simf +++ /dev/null @@ -1,26 +0,0 @@ -mod math { - pub mod ops { - pub fn double(x: u32) -> u32 { - let (_, res): (bool, u32) = jet::add_32(x, x); - res - } - } -} - -mod business_logic { - use crate::math::ops::double; - - pub fn calculate_fee(base_price: u32, tax: u32) -> u32 { - let (_, res): (bool, u32) = jet::add_32(double(base_price), tax); - res - } -} - -use crate::business_logic::calculate_fee; -fn main() { - let price: u32 = 15; - let tax: u32 = 5; - - let total: u32 = calculate_fee(price, tax); - assert!(jet::eq_32(total, 35)); -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf deleted file mode 100644 index cd934f4d..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/flattened.simf +++ /dev/null @@ -1,34 +0,0 @@ -mod unit_2 { - pub fn hash(x: u32, y: u32) -> u32 { - jet::xor_32(x, y) - } -} - -mod unit_1 { - use crate::unit_2::hash as temp_hash; - pub fn get_root(tx1: u32, tx2: u32) -> u32 { - temp_hash(tx1, tx2) - } - pub fn hash(tx1: u32, tx2: u32) -> u32 { - jet::and_32(tx1, tx2) - } -} - -// main unit -mod unit_0 { - use crate::unit_1::{get_root, hash as and_hash}; - use crate::unit_2::hash as or_hash; - - pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { - let root: u32 = get_root(tx1, tx2); - or_hash(prev_hash, root) - } - - fn main() { - let block_val_hash: u32 = get_block_value_hash(5, 10, 20); - assert!(jet::eq_32(block_val_hash, 27)); - let first_value: u32 = 15; - let second_value: u32 = 22; - assert!(jet::eq_32(and_hash(first_value, second_value), 6)); - } -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf deleted file mode 100644 index cead0852..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/main.simf +++ /dev/null @@ -1,16 +0,0 @@ -use merkle::build_root::{get_root, hash as and_hash}; -use base_math::simple_op::hash as or_hash; - -pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { - let root: u32 = get_root(tx1, tx2); - or_hash(prev_hash, root) -} - -fn main() { - let block_val_hash: u32 = get_block_value_hash(5, 10, 20); - assert!(jet::eq_32(block_val_hash, 27)); - - let first_value: u32 = 15; - let second_value: u32 = 22; - assert!(jet::eq_32(and_hash(first_value, second_value), 6)); -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf deleted file mode 100644 index f41c37e4..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/merkle/build_root.simf +++ /dev/null @@ -1,9 +0,0 @@ -use math::simple_op::hash as temp_hash; - -pub fn get_root(tx1: u32, tx2: u32) -> u32 { - temp_hash(tx1, tx2) -} - -pub fn hash(tx1: u32, tx2: u32) -> u32 { - jet::and_32(tx1, tx2) -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf deleted file mode 100644 index 0e463d89..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/crypto/hashes.simf +++ /dev/null @@ -1,5 +0,0 @@ -pub fn sha256(data: u32) -> u256 { - let ctx: Ctx8 = jet::sha_256_ctx_8_init(); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data); - jet::sha_256_ctx_8_finalize(ctx) -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf deleted file mode 100644 index 8f5e77f2..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/flattened.simf +++ /dev/null @@ -1,24 +0,0 @@ -mod unit_1 { - pub fn add(a: u32, b: u32) -> u32 { - let (_, res): (bool, u32) = jet::add_32(a, b); - res - } -} - -mod unit_2 { - pub fn sha256(data: u32) -> u256 { - let ctx: Ctx8 = jet::sha_256_ctx_8_init(); - let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data); - jet::sha_256_ctx_8_finalize(ctx) - } -} - -mod unit_0 { - use crate::unit_1::add; - use crate::unit_2::sha256; - - fn main() { - let sum: u32 = add(2, 3); - let hash: u256 = sha256(sum); - } -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf deleted file mode 100644 index 81031d4c..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/main.simf +++ /dev/null @@ -1,7 +0,0 @@ -use math::arithmetic::add; -use crypto::hashes::sha256; - -fn main() { - let sum: u32 = add(2, 3); - let hash: u256 = sha256(sum); -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf deleted file mode 100644 index 2f348e0c..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_multidep/math/arithmetic.simf +++ /dev/null @@ -1,4 +0,0 @@ -pub fn add(a: u32, b: u32) -> u32 { - let (_, res): (bool, u32) = jet::add_32(a, b); - res -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/math/simple_op.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_op.simf similarity index 100% rename from fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/multiple_deps/math/simple_op.simf rename to fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/simple_op.simf diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf deleted file mode 100644 index ef182924..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/flattened.simf +++ /dev/null @@ -1,29 +0,0 @@ -mod unit_2 { - pub type Smth = u32; - - pub fn get_five() -> u32 { - 5 - } -} - -mod unit_1 { - pub use crate::unit_2::Smth; - - pub fn two() -> Smth { - 2 - } -} - -mod unit_0 { - pub use crate::unit_1::two as smth; - use crate::unit_2::{get_five, Smth}; - - fn seven() -> u32 { - 7 - } - - fn main() { - let (_, temp): (bool, u32) = jet::add_32(smth(), get_five()); - assert!(jet::eq_32(temp, seven())); - } -} diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf deleted file mode 100644 index 7897ab8a..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/main.simf +++ /dev/null @@ -1,11 +0,0 @@ -pub use temp::constants::utils::two as smth; -use temp::funcs::{get_five, Smth}; - -fn seven() -> u32 { - 7 -} - -fn main() { - let (_, temp): (bool, u32) = jet::add_32(smth(), get_five()); - assert!(jet::eq_32(temp, seven())); -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf deleted file mode 100644 index 4fd2102e..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/constants/utils.simf +++ /dev/null @@ -1,5 +0,0 @@ -pub use crate::funcs::Smth; - -pub fn two() -> Smth { - 2 -} \ No newline at end of file diff --git a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf b/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf deleted file mode 100644 index 0ff2da55..00000000 --- a/fixtures/deps/SimplicityHL-eb2eb9829a53bfc7/examples/single_dep/temp/funcs.simf +++ /dev/null @@ -1,5 +0,0 @@ -pub type Smth = u32; - -pub fn get_five() -> u32 { - 5 -} \ No newline at end of file diff --git a/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf b/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf index 214fc97d..36fc90f1 100644 --- a/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf +++ b/fixtures/deps/test-simplex-crate-3a0ece279ca58451/test/smth.simf @@ -1 +1 @@ -pub use hl_compiler::multiple_deps::math::simple_op::hash; \ No newline at end of file +pub use hl_compiler::simple_op::hash; \ No newline at end of file From 5d46d8074a71e1325ea54d1dadbb560ab4613cf9 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 16 Jun 2026 17:20:14 +0300 Subject: [PATCH 08/10] fix: apply linting suggestions from review --- crates/build/src/config.rs | 3 +++ crates/build/src/error.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/crates/build/src/config.rs b/crates/build/src/config.rs index 4d144f90..c0fa1e83 100644 --- a/crates/build/src/config.rs +++ b/crates/build/src/config.rs @@ -71,7 +71,9 @@ impl DependencyConfig { Some(section) => section.clone().try_into()?, None => Self::default(), }; + res.validate()?; + Ok(res) } @@ -83,6 +85,7 @@ impl DependencyConfig { _ => {} } } + Ok(()) } } diff --git a/crates/build/src/error.rs b/crates/build/src/error.rs index 3b57048e..51e4aecf 100644 --- a/crates/build/src/error.rs +++ b/crates/build/src/error.rs @@ -11,6 +11,7 @@ use globwalk::GlobError; pub enum DependencyValidationError { #[error("Invalid dependency '{0}': you must specify either a 'path' or a 'git' repository")] Missing(String), + #[error("Invalid dependency '{0}': cannot specify both 'path' and 'git', choose one")] Conflicting(String), } From 71268286dd340c220be877fb58a123899ed3bdb8 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 16 Jun 2026 18:09:09 +0300 Subject: [PATCH 09/10] fix: linter --- crates/build/src/config.rs | 7 ++++--- crates/build/src/generator.rs | 4 ++-- crates/build/src/resolver.rs | 1 + crates/cli/src/commands/install.rs | 9 ++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/build/src/config.rs b/crates/build/src/config.rs index c0fa1e83..a8b4deb2 100644 --- a/crates/build/src/config.rs +++ b/crates/build/src/config.rs @@ -42,6 +42,7 @@ impl BuildConfig { /// top-level keys. If the section is absent, returns [`BuildConfig::default`]. pub fn from_source(content: &str) -> Result { let table: toml::Table = toml::from_str(content)?; + match table.get("build") { Some(section) => Ok(section.clone().try_into()?), None => Ok(Self::default()), @@ -78,10 +79,10 @@ impl DependencyConfig { } pub fn validate(&self) -> Result<(), DependencyValidationError> { - for (drp_name, dep) in &self.inner { + for (dep_name, dep) in &self.inner { match (&dep.path, &dep.git) { - (None, None) => return Err(DependencyValidationError::Missing(drp_name.clone())), - (Some(_), Some(_)) => return Err(DependencyValidationError::Conflicting(drp_name.clone())), + (None, None) => return Err(DependencyValidationError::Missing(dep_name.clone())), + (Some(_), Some(_)) => return Err(DependencyValidationError::Conflicting(dep_name.clone())), _ => {} } } diff --git a/crates/build/src/generator.rs b/crates/build/src/generator.rs index d6925c09..0e63b672 100644 --- a/crates/build/src/generator.rs +++ b/crates/build/src/generator.rs @@ -362,8 +362,8 @@ impl ArtifactsGenerator { let hash_value = hasher.finish(); // Do it the Rust way: EXACTLY 16 hex characters - let folder_name = format!("{}-{:016x}", repo_name, hash_value); + let dir_name = format!("{}-{:016x}", repo_name, hash_value); - Some(PathBuf::from(folder_name)) + Some(PathBuf::from(dir_name)) } } diff --git a/crates/build/src/resolver.rs b/crates/build/src/resolver.rs index 6398f667..3258fa12 100644 --- a/crates/build/src/resolver.rs +++ b/crates/build/src/resolver.rs @@ -116,6 +116,7 @@ impl ArtifactsResolver { let Ok(parsed_program) = parse::Program::parse_from_str(source) else { return false; }; + Self::rec_main_checker(parsed_program.items(), &FunctionName::main()) } diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index 2cd2a748..dffa5ef6 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -53,10 +53,10 @@ impl Install { continue; }; - let hashed_folder = ArtifactsGenerator::generate_hashed_repo_path(git_repo_url) + let hashed_dir = ArtifactsGenerator::generate_hashed_repo_path(git_repo_url) .ok_or_else(|| InstallError::InvalidUrl(git_repo_url.clone()))?; - let target_dir = deps_dir.join(hashed_folder); + let target_dir = deps_dir.join(hashed_dir); if !target_dir.exists() { fs::create_dir_all(&target_dir).map_err(|e| InstallError::CreateDir(e, target_dir.clone()))?; @@ -69,7 +69,7 @@ impl Install { // Consider it installed and skip cloning. if is_empty { - // Assumes the folder is perfectly empty + // Assumes the directory is perfectly empty let status = Command::new("git") .arg("clone") .arg("--depth") @@ -100,15 +100,18 @@ impl Install { impl Display for InstalledRepos { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { write!(f, "[")?; + for (index, path) in self.0.iter().enumerate() { if index > 0 { write!(f, ",")?; } write!(f, "\n {}", path.display())?; } + if !self.0.is_empty() { writeln!(f)?; } + write!(f, "]") } } From bfebe56afb359a4f092226fe7f7f3edc56531cdc Mon Sep 17 00:00:00 2001 From: Artem Chystiakov Date: Tue, 16 Jun 2026 19:05:34 +0300 Subject: [PATCH 10/10] upd readme --- README.md | 8 +++++++- examples/basic/Cargo.lock | 35 ++++++--------------------------- examples/basic/Simplex.toml | 3 +++ examples/basic/simf/bip340.simf | 3 +++ examples/basic/simf/p2pk.simf | 4 +++- 5 files changed, 22 insertions(+), 31 deletions(-) create mode 100644 examples/basic/simf/bip340.simf diff --git a/README.md b/README.md index db345764..22d26fff 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,9 @@ src_dir = "./simf" simf_files = ["*.simf"] out_dir = "./src/artifacts" +[dependencies] +some_dep = { git = "", path = "" } + [regtest] mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" bitcoins = 10_000_000 @@ -85,6 +88,8 @@ Where: - `src_dir` - The simplicity contracts source directory. - `simf_files` - A glob pattern indicating which contracts are in scope. - `out_dir` - The output directory where contracts artifacts are generated. +- `dependencies` (`simplex install` config) + - The list of SimplicityHL dependencies to install. - `regtest` (`simplex regtest` config) - `mnemonic` - The signer's mnemonic regtest will send initial funds to. - `bitcoins` - Initial coins available to the signer. @@ -110,6 +115,7 @@ Simplex CLI provides the following commands: - `simplex init` - Initializes a new Simplex project. - `simplex config` - Prints the current config. +- `simplex install` - Installs specified SimplicityHL dependencies. - `simplex build` - Generates simplicity artifacts. - `simplex regtest` - Spins up local Electrs + Elements nodes. - `simplex test` - Runs Simplex tests. @@ -133,11 +139,11 @@ We are open to any mind-blowing ideas! Please take a look at our [contributing g - [x] Complete `simplex init` and `simplex clean` tasks. - [x] Simplicity storage compatibility. +- [x] Simplicity dependencies management. - [ ] SDK support for confidential assets, taproot signer, and custom witness signatures. - [ ] Local regtest 10x speedup. - [ ] Regtest cheat codes. - [ ] Browser compatibility. -- [ ] Simplicity dependencies management. - [ ] Comprehensive documentation. Check out the full roadmap [here](https://github.com/orgs/BlockstreamResearch/projects/3). diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index 9b1edef3..20e816af 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -921,18 +921,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.14", - "regex-syntax 0.8.10", -] - [[package]] name = "regex-automata" version = "0.3.9" @@ -1044,15 +1032,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "santiago" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de36022292bc2086eb8f55bffa460fef3475e4459b478820711f4c421feb87ec" -dependencies = [ - "regex", -] - [[package]] name = "sct" version = "0.7.1" @@ -1192,9 +1171,9 @@ dependencies = [ [[package]] name = "simplicity-lang" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e57bd4d84853974a212eab24ed89da54f49fbccf5e33e93bcd29f0a6591cd5" +checksum = "13ed081e3046d66c146d7201bcbf3b655ca3436cb83f6efc26d7895bd2b79d06" dependencies = [ "bitcoin", "bitcoin_hashes", @@ -1204,15 +1183,14 @@ dependencies = [ "ghost-cell", "hex-conservative", "miniscript", - "santiago", "simplicity-sys", ] [[package]] name = "simplicity-sys" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3401ee7331f183a5458c0f5a4b3d5d00bde0fd12e2e03728c537df34efae289" +checksum = "96d1ec5477c7650b8ef511aa56dccb28f2e8cdb6e87f260ecffdaf0ebfef2d3b" dependencies = [ "bitcoin_hashes", "cc", @@ -1220,9 +1198,8 @@ dependencies = [ [[package]] name = "simplicityhl" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25de8990174fe3e1a843df138cacc4265d05839ebd2550c18b9196f567d55e81" +version = "0.6.0-rc.0" +source = "git+https://github.com/BlockstreamResearch/SimplicityHL.git#ee7234280fdb2cfcb2cc4285a5709ca2808c5593" dependencies = [ "base64 0.21.7", "chumsky", diff --git a/examples/basic/Simplex.toml b/examples/basic/Simplex.toml index e8eff361..c522f64c 100644 --- a/examples/basic/Simplex.toml +++ b/examples/basic/Simplex.toml @@ -5,6 +5,9 @@ # simf_files = ["*.simf"] # out_dir = "./src/artifacts" +# [dependencies] +# some_dep = { git = "", path = "" } + # [regtest] # mnemonic = "exist carry drive collect lend cereal occur much tiger just involve mean" # bitcoins = 10_000_000 diff --git a/examples/basic/simf/bip340.simf b/examples/basic/simf/bip340.simf new file mode 100644 index 00000000..5749c706 --- /dev/null +++ b/examples/basic/simf/bip340.simf @@ -0,0 +1,3 @@ +pub fn verify_sig(pk: Pubkey, sig: Signature) { + jet::bip_0340_verify((pk, jet::sig_all_hash()), sig); +} diff --git a/examples/basic/simf/p2pk.simf b/examples/basic/simf/p2pk.simf index f6a75e67..a8489b2e 100644 --- a/examples/basic/simf/p2pk.simf +++ b/examples/basic/simf/p2pk.simf @@ -1,3 +1,5 @@ +use crate::bip340::verify_sig; + fn main() { - jet::bip_0340_verify((param::PUBLIC_KEY, jet::sig_all_hash()), witness::SIGNATURE) + verify_sig(param::PUBLIC_KEY, witness::SIGNATURE); }