Skip to content
Merged

Dev #191

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
55430d6
rewrote fuzz assertions
Arvolear Oct 12, 2026
d13eac6
clean up
Arvolear Oct 12, 2026
59b1c92
signing checks
Arvolear Oct 12, 2026
3496def
clean up
Arvolear Oct 13, 2026
3a54c51
added scenarios
Arvolear Oct 14, 2026
019a2b0
clean up
Arvolear Oct 14, 2026
6a139c9
bindings and refactor
Arvolear Oct 14, 2026
56fda6d
add storage
Arvolear Oct 15, 2026
61abd56
add smart lock check
Arvolear Oct 16, 2026
6271b20
clean up
Arvolear Oct 16, 2026
a046368
fuzzing spending path
Arvolear Oct 16, 2026
ee9864e
quick path check
Arvolear Oct 17, 2026
0e5b00e
helper functions
Arvolear Oct 19, 2026
012df81
witness requirements
Arvolear Oct 19, 2026
4e16c33
optimize tests
Arvolear Oct 19, 2026
3a59f08
basic mutation
Arvolear Oct 20, 2026
f1b8374
more mutations
Arvolear Oct 20, 2026
5515de7
topology mutations
Arvolear Oct 20, 2026
24e1e9b
refactor 1
Arvolear Oct 20, 2026
7fa0c5d
refactor 2
Arvolear Oct 20, 2026
c00e6b9
optimization
Arvolear Oct 20, 2026
3eec0f4
API clean up
Arvolear Oct 20, 2026
49085b6
sdk optimization
Arvolear Oct 21, 2026
04b9058
signing todo
Arvolear Oct 21, 2026
4c092da
fee estimation optimization
Arvolear Oct 21, 2026
4a458ee
fix sequence issue
Arvolear Oct 21, 2026
9b28abf
cleaned up node requests
Arvolear Oct 21, 2026
cdd9ef1
errors and panics
Arvolear Oct 21, 2026
e97393d
types clean up
Arvolear Oct 21, 2026
700f11d
smell clean up
Arvolear Oct 21, 2026
72938e9
Merge pull request #187 from BlockstreamResearch/feat/sdk-fixes
Arvolear Oct 21, 2026
dc7b26d
Merge pull request #184 from BlockstreamResearch/feat/smarter-fuzzing
Arvolear Oct 21, 2026
a42e180
fix: unblinding issues when input secret is missing
ikripaka Oct 20, 2026
2c1c551
fix: inconsistent git and hashing behaviour
ikripaka Oct 21, 2026
1b0829e
fix: lints
ikripaka Oct 22, 2026
71ef9fa
lint
Arvolear Oct 23, 2026
4eb01f8
fix sequence issue
Arvolear Oct 23, 2026
a0a994c
Merge pull request #186 from BlockstreamResearch/fix/issues
Arvolear Oct 23, 2026
a5573a2
fix change selection
Arvolear Oct 26, 2026
1739c37
various fixes
Arvolear Oct 26, 2026
a71e5a0
build fixes
Arvolear Oct 26, 2026
f8aec49
fix simplexup
Arvolear Oct 26, 2026
ef334c8
misleading comment
Arvolear Oct 26, 2026
9bf4a3f
review fixes
Arvolear Oct 27, 2026
2312b5b
Merge pull request #188 from BlockstreamResearch/fix/findings
Arvolear Oct 27, 2026
1beabf6
fixtures: format contracts
ikripaka Sep 8, 2026
3719b21
CI: update release workflow (include simfmt)
ikripaka Sep 9, 2026
78a4dbe
simplexup: include simfmt binary
ikripaka Sep 9, 2026
b421082
fmt: add formatting functionality into a cli
ikripaka Sep 10, 2026
81fc0a5
readme: add `simplex fmt` command brief description
ikripaka Oct 2, 2026
6aa2383
fix(cli): remove "--version" option from format option
ikripaka Oct 5, 2026
c3529be
fix(cli): remove "--manifet-path" option from format option
ikripaka Oct 5, 2026
79a9ae1
fix(cli): move "--check" option higher
ikripaka Oct 5, 2026
7922173
fix(cli): add one "--short" field instead of "--message-format"
ikripaka Oct 5, 2026
a888402
fix(ci): move variables around in publish CI
ikripaka Oct 5, 2026
7992e39
apply suggestions
ikripaka Oct 29, 2026
9ab4e09
fix comments, shorten fmt invocation
ikripaka Oct 29, 2026
2503624
standardize relative directory resolving
ikripaka Oct 29, 2026
11e5121
wording fixes
Arvolear Oct 30, 2026
a987fb1
Merge pull request #150 from BlockstreamResearch/feat/fmt
Arvolear Oct 30, 2026
e934ee5
review fixes
Arvolear Oct 30, 2026
1925eef
changelog
Arvolear Oct 30, 2026
69086f2
versions
Arvolear Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,23 @@ jobs:
cargo build --target $TARGET --profile $RUST_PROFILE --bins
mv $OUT_DIR/${{ env.RUST_PROFILE }}/simplex simplex

- name: Download nextest, elementsd and electrs
- name: Download nextest, simfmt, elementsd and electrs
env:
NEXTEST_VERSION: "0.9.137"
ELEMENTSD_VERSION: "23.3.1"
SIMFMT_VERSION: "0.0.2"
ELEMENTSD_TARGET: ${{ matrix.elementsd_target }}
ELECTRS_TARGET: ${{ matrix.electrs_target }}
NEXTEST_TARGET: ${{ matrix.nextest_target }}
SIMFMT_TARGET: ${{ matrix.target }}
run: |
#!/usr/bin/env bash
set -eo pipefail

ELEMENTSD_FILENAME="elements-${ELEMENTSD_VERSION}-${ELEMENTSD_TARGET}.tar.gz"
ELECTRS_FILENAME="electrs_${ELECTRS_TARGET}_esplora_027e38d3ebc2f85b28ae76f8f3448438ee4fc7b1_liquid.zip"
NEXTEST_FILENAME="cargo-nextest-${NEXTEST_VERSION}-${NEXTEST_TARGET}.tar.gz"
SIMFMT_FILENAME="simfmt-v${SIMFMT_VERSION}-${SIMFMT_TARGET}.tar.gz"

curl -Ls "https://github.com/ElementsProject/elements/releases/download/elements-${ELEMENTSD_VERSION}/${ELEMENTSD_FILENAME}" -o ${ELEMENTSD_FILENAME}
tar -xzf ${ELEMENTSD_FILENAME} && rm ${ELEMENTSD_FILENAME}
Expand All @@ -90,6 +96,9 @@ jobs:
curl -Ls "https://github.com/nextest-rs/nextest/releases/download/cargo-nextest-${NEXTEST_VERSION}/${NEXTEST_FILENAME}" -o ${NEXTEST_FILENAME}
tar -xzf ${NEXTEST_FILENAME} && rm ${NEXTEST_FILENAME}
mv cargo-nextest smplx-nextest

curl -fLsS "https://github.com/BlockstreamResearch/simfmt/releases/download/v${SIMFMT_VERSION}/${SIMFMT_FILENAME}" -o "${SIMFMT_FILENAME}"
tar -xzf "${SIMFMT_FILENAME}" && rm "${SIMFMT_FILENAME}"

- name: Sign elementsd binary
run: |
Expand All @@ -106,6 +115,7 @@ jobs:
elementsd
electrs
smplx-nextest
simfmt

- name: Record attestation URL
env:
Expand All @@ -116,7 +126,7 @@ jobs:
- name: Archive binaries
env:
ARCHIVE: simplex-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.archive }}
run: tar czf ${ARCHIVE} simplex elementsd electrs smplx-nextest
run: tar czf ${ARCHIVE} simplex elementsd electrs smplx-nextest simfmt

- name: Upload build artifacts
uses: actions/upload-artifact@v6
Expand Down Expand Up @@ -163,11 +173,14 @@ jobs:
contents: read
id-token: write
concurrency:
group: publish-core-${{ github.ref }}
group: publish-core-${{ github.event.inputs.tag || github.ref }}
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.event.inputs.tag || github.ref }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
# Changelog

## [0.2.0]

- Added `simplex fmt` to format SimplicityHL sources with `simfmt`, which `simplexup` now installs.
- Rewrote fuzz testing to a much more versatile and smart suite.
- Made every command find `Simplex.toml` in the current directory or any of its ancestors.

### fuzz

- Replaced the single-program fuzz engine with `Scenario`, a transaction template where each program `spends` inputs and `locks` outputs.
- Added `bind_arguments` and `bind_witness` to derive values from the case, and `requires_witness` for values the fuzzer also violates to check that the contract rejects them.
- Added `spec` to state when a case must be accepted, rejected or either, read from `FuzzCase`.
- Added transaction `Mutations` of amounts, assets, timelocks and layout, applied to the template's targeted inputs and outputs.
- Added lock and authorization smart checks that redirect locked outputs and replay signatures from a wrong key.
- Added `spending_path` and `storage_capacity` to the program builder, and `random_strategy` and `custom_strategy` to replace the guided default.
- Added a run summary of verdicts, rejections, spec predictions, violations, mutations and smart checks.
- Removed `FuzzableProgram`, `FuzzTransaction`, `ArgsStrategyBuilder`, `ProgramCheck` and `FuzzError`.

### sdk

- Replaced `RequiredSignature` arguments with `Option<NativeSignature>` for native inputs and `Option<ProgramSignature>` for program inputs, built as `ProgramSignature::new(name).at_path(path).tagged(tag)`.
- Made `Signer` sign only inputs that request a native signature and refuse a key that doesn't own a P2WPKH input.
- Made `Signer` derive its keys and addresses once, and reuse one sighash cache per transaction.
- Reduced how often coin selection signs the transaction.
- Fixed coin selection: skip UTXOs listed twice, skip confidential UTXOs for an explicit change target, and keep blinded change that would reveal a blinded amount.
- Fixed fees: report the whole leftover as the fee when change is dropped, and fold explicit change below the 22 sat dust threshold into the fee.
- Made a set locktime enforced by default, by giving inputs without a sequence `ENABLE_LOCKTIME_NO_RBF`.
- Made per-input `sequence` and `locktime` optional, so an explicit `Sequence::MAX` or `LockTime::ZERO` is kept.
- Made `get_utxos` skip outputs the wallet can't unblind, instead of failing.
- Replaced panics with errors for unknown input amounts, amount overflows, invalid fee rates, duplicate inputs, empty issuances, unblinded reissuance tokens, inputs without a witness UTXO and invalid derivation paths.
- Fixed token-only issuance by encoding a zero asset amount as null.
- Added `FinalTransaction::sequence` and `locktime` getters.
- Added `Program::set_storage`, `get_arguments` and `MAX_STORAGE_SLOTS`, and capped storage at 128 slots.
- Made `SimplexProvider::broadcast_transaction` wait until Esplora indexes the transaction.
- Cached the program's CMR, witness types and taproot spend info.

### build

- Refused a dependency `src_dir` that resolves outside its package.
- Refused an `out_dir` that holds the project or its `src_dir`, in both `build` and `clean`.
- Refused source directories that aren't Rust identifiers, before writing any artifact.
- Warned about `.simf` files that don't parse, instead of skipping them silently.

### install

- Resolved tags and branches by their full ref.
- Made git dependency directory names unambiguous.
- Made installs atomic, and reported an incomplete dependency instead of treating it as installed.

### init

- Made `simplex init` optimize dependencies in dev builds, which speeds up tests.

### wasm

- Rejected issuances of zero, reissuance tokens with a zero blinding factor, derivation paths other than `m/n`, and more than 128 extra leaves.

### simplexup

- Verified binaries before installing them and refused versions whose install didn't complete.
- Verified the attestation's signer with `gh` when the GitHub CLI is installed and logged in.
- Installed `--commit` builds as their own version.
- Made `--use` switch every binary to the chosen version.

## [0.1.1]

- Fixed broken dependencies.
Expand Down
17 changes: 8 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ license = "MIT"
edition = "2024"

[workspace.dependencies]
smplx-macros = { path = "./crates/macros", version = "=0.1.1" }
smplx-build = { path = "./crates/build", version = "=0.1.1" }
smplx-test = { path = "./crates/test", version = "=0.1.1" }
smplx-regtest = { path = "./crates/regtest", version = "=0.1.1" }
smplx-sdk = { path = "./crates/sdk", version = "=0.1.1", default-features = false }
smplx-std = { path = "./crates/simplex", version = "=0.1.1" }
smplx-wasm = { path = "./crates/wasm", version = "=0.1.1" }
smplx-macros = { path = "./crates/macros", version = "=0.2.0" }
smplx-build = { path = "./crates/build", version = "=0.2.0" }
smplx-test = { path = "./crates/test", version = "=0.2.0" }
smplx-regtest = { path = "./crates/regtest", version = "=0.2.0" }
smplx-sdk = { path = "./crates/sdk", version = "=0.2.0", default-features = false }
smplx-std = { path = "./crates/simplex", version = "=0.2.0" }
smplx-wasm = { path = "./crates/wasm", version = "=0.2.0" }

rand_core = { version = "=0.10.1" }
rand = { version = "=0.10.3" }
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Simplex CLI provides the following commands:
- `simplex regtest` - Spins up local Electrs + Elements nodes.
- `simplex test` - Runs Simplex tests. Provide a `--fuzz` flag to run Simplex fuzz tests.
- `simplex clean` - Cleans up generated artifacts and installed dependencies.
- `simplex fmt` - Formats SimplicityHL files according to the specified `simfmt.toml` or default [configuration](https://github.com/BlockstreamResearch/simfmt/blob/master/simfmt.toml).

To view the available options, run the help command:

Expand All @@ -150,11 +151,12 @@ We are open to any mind-blowing ideas! Please take a look at our [contributing g
- [x] SimplicityHL storage compatibility.
- [x] SimplicityHL dependencies management.
- [x] Fuzz testing for SimplicityHL contracts.
- [ ] SDK support for confidential assets, taproot signer, and custom witness signatures.
- [x] Browser compatibility.
- [ ] SDK support for taproot signer and custom descriptors.
- [ ] SimplicityHL contracts static analyzer.
- [ ] Test coverage reporting.
- [ ] Local regtest 10x speedup.
- [ ] Regtest cheat codes.
- [ ] Browser compatibility.
- [ ] Comprehensive documentation.

Check out the full roadmap [here](https://github.com/orgs/BlockstreamResearch/projects/3).
Expand Down
2 changes: 1 addition & 1 deletion crates/build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "smplx-build"
version = "0.1.1"
version = "0.2.0"
description = "Simplex build command internal implementation"
license.workspace = true
edition.workspace = true
Expand Down
9 changes: 9 additions & 0 deletions crates/build/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ impl DepCollector {
let loaded_simf_dir = CanonPath::canonicalize(&loaded_context.as_path().join(loaded_src_dir))
.map_err(BuildError::PathCanonicalization)?;

// A dependency may only define import contexts inside its own package.
if !loaded_simf_dir.starts_with(&loaded_context) {
return Err(BuildError::DependencySourceOutsidePackage {
dep_name: dep_name.clone(),
package_root: loaded_context.as_path().to_path_buf(),
source_dir: loaded_simf_dir.as_path().to_path_buf(),
});
}

self.builder
.add_dependency(simf_dir.clone(), dep_name.clone(), loaded_simf_dir.clone());

Expand Down
15 changes: 15 additions & 0 deletions crates/build/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ pub enum BuildError {
#[error("Invalid generation path: '{0}'")]
GenerationPath(String),

#[error("out_dir '{0}' holds the project or its src_dir")]
OutDirHoldsSources(PathBuf),

#[error("Directory '{name}' in '{path}' must be a Rust identifier to become a module of the bindings")]
InvalidModuleName { name: String, path: PathBuf },

#[error("Failed to extract content from path, err: '{0}'")]
FailedToExtractContent(io::Error),

Expand All @@ -112,6 +118,15 @@ pub enum BuildError {
#[error("Failed to build dependency map: {0}")]
DependencyMap(String),

#[error(
"Dependency '{dep_name}' src_dir resolves outside its package: '{source_dir}' (package root: '{package_root}')"
)]
DependencySourceOutsidePackage {
dep_name: String,
package_root: PathBuf,
source_dir: PathBuf,
},

#[error("Failed to flatten program: {0}")]
Flattening(String),

Expand Down
53 changes: 53 additions & 0 deletions crates/build/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ impl ArtifactsGenerator {
let simf_out_dir = out_dir.join(pathdiff);
let mut metadata = Metadata::default();

for simf in simfs {
Self::check_module_names(simf.as_ref(), base_dir)?;
}

let artifacts = simfs
.iter()
.map(|s| Self::process_simf(s.as_ref(), base_dir, validated_deps, &simf_out_dir, &mut metadata))
Expand Down Expand Up @@ -177,6 +181,29 @@ impl ArtifactsGenerator {
})
}

/// Every directory between `base_dir` and `source` becomes a module of the bindings.
fn check_module_names(source: &Path, base_dir: &Path) -> Result<(), BuildError> {
let relative_path = source
.strip_prefix(base_dir)
.map_err(BuildError::NoBasePathForGeneration)?;

for dir in relative_path.parent().into_iter().flat_map(Path::components) {
let Component::Normal(name) = dir else {
continue;
};
let name = name.to_string_lossy();

if syn::parse_str::<syn::Ident>(&name).is_err() {
return Err(BuildError::InvalidModuleName {
name: name.into_owned(),
path: source.to_path_buf(),
});
}
}

Ok(())
}

/// Arranges a flat list of artifacts into a tree mirroring the source directory layout.
fn build_tree(artifacts: Vec<SimfArtifact>) -> Result<TreeNode, BuildError> {
let mut root = TreeNode::default();
Expand Down Expand Up @@ -391,3 +418,29 @@ impl ArtifactsGenerator {
Ok(code)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn directories_must_be_rust_identifiers() {
let base = Path::new("/project/simf");

for valid in ["vault.simf", "vaults/vault.simf", "vaults/_2024/vault.simf"] {
assert!(
ArtifactsGenerator::check_module_names(&base.join(valid), base).is_ok(),
"{valid}"
);
}
for invalid in ["my-contracts/vault.simf", "2024/vault.simf", "mod/vault.simf"] {
assert!(
matches!(
ArtifactsGenerator::check_module_names(&base.join(invalid), base),
Err(BuildError::InvalidModuleName { .. })
),
"{invalid}"
);
}
}
}
Loading
Loading