Strata: unaudited, testnet-only Soroban smart contracts on Stellar that split a vault's yield into senior and junior tranches
A senior/junior tranche wrapper for fixed-term epochs.
Strata is unaudited and testnet-only. Nothing in this repository has been reviewed by a third party. Do not deploy it to mainnet. Do not put real funds into it. The arithmetic is property-tested and the documented limits are derived rather than guessed, but that is not an audit and it is not a security review. See
docs/risks.mdfor what is known to be unresolved, including four bugs that shipped and were caught by tests.
A vault wrapper that splits the result of one fixed-term epoch between two tranches:
- Senior β principal plus a fixed target rate, capped at that target, paid first.
- Junior β everything above the senior target, absorbing losses first.
The problem it addresses is that a fixed-rate promise and a variable underlying pull in opposite directions. If the underlying earns less than the target, someone has to take the shortfall; most structures let that land on the senior holder by accident. Strata makes the junior tranche structurally senior instead: deposits are gated so the junior buffer is always at least as large as the senior principal, and settlement pays the senior first out of whatever the vault actually returned.
The interesting part is what it does not contain. There are no fees, no
re-entrancy surface of its own, no transferable positions, and no partial
withdrawal. All of the money-handling arithmetic lives in one small pure-Rust
crate with no soroban-sdk dependency at all, which is what makes it cheap to
property-test and impossible to disagree with the contract.
It is not a yield product. The vault it wraps is a mock whose yield an admin
sets by hand. There is no real yield source integrated. A second vault β
contracts/blend-adapter, which supplies to a real
Blend lending pool β is built and tested against a pool test double, including
full epochs driven by the real epoch manager through good, flat, loss and
liquidity-limited settlements. It has also been deployed on Stellar testnet
against the real Blend pool and has settled one 300-second epoch β 2 XLM in,
20 000 451 stroops out, so 451 stroops of yield β documented in
docs/deployment.md. That run proves the plumbing only;
it says nothing about liquidity or losses. See risks.md B1βB7.
ββββββββββββββββββββ
β waterfall β pure i128 settlement math
β no dependencies β no Env, no storage, no SDK
ββββββββββ¬ββββββββββ
β
ββββββββββ΄ββββββββββ βββββββββββββββββββββ
β epoch-manager βββββββββΆβ vault-interface β
β β β the ERC-4626 β
β owns the money β β subset Strata β
β and the state β β needs β
β machine β βββββββββββ¬ββββββββββ
ββββββββββ¬ββββββββββ β
β ββββββββββ΄ββββββββββ
βββββββββββββββββββββΆβ mock-vault β
β admin-set signedβ
β yield (testnet) β
ββββββββββββββββββββ
The contract owns the money and the state machine. It owns none of the
arithmetic: every payout figure comes from the waterfall crate. That is why
there is exactly one implementation of the split to audit β and why
contracts/waterfall/src/lib.rs is the first
file to read.
Details in docs/architecture.md. The settlement math and
its five invariants are specified in
docs/waterfall-spec.md, which is the source of truth β
code that disagrees with it is a bug, and changing it needs a spec issue.
S = J = 1_000_000, senior target 5%/yr, one-year term, so the senior's target is
1_050_000. If the vault returns V:
V |
senior_payout | junior_payout | what happened |
|---|---|---|---|
| 2_200_000 | 1_050_000 | 1_150_000 | healthy; senior capped, junior takes the rest |
| 1_100_000 | 1_050_000 | 50_000 | a 900_000 loss, fully absorbed by the buffer |
| 1_049_999 | 1_049_999 | 0 | one unit past the cushion; senior starts paying |
| 900_000 | 900_000 | 0 | junior wiped, senior covers the shortfall |
| 0 | 0 | 0 | total wipeout |
senior_payout + junior_payout == V always. The senior never beats its target.
The junior is paid nothing unless the senior was made whole first.
The cushion is J - I, where I is the senior's own target interest β not J.
A buffer exactly equal to I leaves zero cushion. This is the most misreadable
property of the design; see the spec
and risks.md. A senior depositor can lose their entire principal
in a wipeout. "Senior" means paid first, not safe.
Requires a Rust toolchain (pinned in rust-toolchain.toml) and, for the Wasm
build, the Stellar CLI. A prebuilt binary from the official
stellar-cli releases page is
much faster to install than cargo install stellar-cli.
git clone https://github.com/strata-protocol/strata-contracts
cd strata-contracts
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all --locked
# Stronger property testing, which is where a settlement bug would surface.
PROPTEST_CASES=20000 cargo test --release -p strata-testscargo test needs no network access and no credentials. All five of the checks
above were run against this tree.
stellar contract build
# -> target/wasm32v1-none/release/strata_epoch_manager.wasm (25964 bytes)
# -> target/wasm32v1-none/release/strata_mock_vault.wasm ( 9638 bytes)
# -> target/wasm32v1-none/release/strata_blend_adapter.wasm (21345 bytes)cargo build does not produce a deployable contract. The Soroban runtime only
accepts wasm32v1-none built by stellar contract build, which also runs wasm-opt
β which is why its artefacts are smaller than a plain cargo build's. CI builds
the real thing and fails if either named artefact is missing.
Both contracts are live. Full guide in docs/deployment.md;
machine-readable record in deployments/testnet.json.
./scripts/deploy-testnet.sh --self-test # no network needed
./scripts/deploy-testnet.sh strata-deployer testnet # fresh deploy
./scripts/run-epoch-demo.sh good # ~6 min, mostly waiting
./scripts/run-epoch-demo.sh loss # ~6 min, mostly waitingEvery script refuses any network but testnet, and CI runs that refusal as a test.
A read-only dashboard shows these contracts' live testnet state: the current epoch, an account's tranche position, and projected payouts.
https://strata-protocol.github.io/strata-app/
Testnet only, unaudited. If testnet has been reset, the dashboard will report that the contracts were not found.
Deployed 2026-10-05 with Stellar CLI 28.1.0 and soroban-sdk 27.0.6, against a protocol 29 testnet. The on-chain Wasm hash of each contract was checked against the sha256 of the locally built artefact and matched exactly.
| Contract | Address | Explorer |
|---|---|---|
epoch-manager |
CB57H6NE7CIPHEDO2HJT7IX55NHP6RXI2EPSUIGK65NLG5CCXC4JB7QU |
stellar.expert |
mock-vault |
CDWJS65BA26QBTA4L6LBX76B2XPGAQHY25USI6Z3MSQ73T5NQTXARSQ6 |
stellar.expert |
| Underlying (native XLM SAC) | CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC |
stellar.expert |
Two complete epochs were settled on that deployment, with every figure checked
against docs/waterfall-spec.md recomputed independently
by the script rather than read back from the contract:
| Scenario | V redeemed |
senior_payout |
junior_payout |
Demonstrates |
|---|---|---|---|---|
good |
2 000 204 528 | 1 000 009 512 | 1 000 195 016 | the cap binding: senior took its 9 512 target, junior the other 195 016 |
loss |
1 999 836 692 | 1 000 009 512 | 999 827 180 | senior made whole, junior absorbed the whole 172 820 loss |
In both, the two payouts summed to V to the stroop. Transaction hashes are in
docs/deployment.md.
Separately, the Blend adapter runs against the real testnet Blend pool with its own epoch manager. The mock-vault deployment above is untouched.
| Contract | Address |
|---|---|
blend-adapter |
CBJIQYHM5SIXY3QMT7X6ARQUPUWSNH3EZ5T3JOILETKBXR6SPWFKAKXI |
epoch-manager (Blend) |
CALMXJCAKZALDPYFFBWQNUSBAHUM7HIL5NSITX7FYUEUSYN4Y4CSFPVT |
| Blend pool | CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF |
One 300-second epoch settled end to end: 2 XLM in, 20 000 451 stroops out. The
yield was 451 stroops; the senior took its 4-stroop target and the junior the
remaining 447, exactly as docs/waterfall-spec.md
recomputed independently gives. The deployed adapter predates refresh(). The
record is
deployments/testnet-blend.json; the full run
is in docs/deployment.md.
Testnet is reset periodically by SDF, so these addresses are only meaningful until the next reset.
Marked Done only where the work was actually run and checked.
| Component | Status | Notes |
|---|---|---|
docs/waterfall-spec.md |
Done | Source of truth. Frozen without a spec issue. |
contracts/waterfall |
Done | Pure settlement math. 19 tests. |
| Five spec invariants | Done | 16 property tests in tests/src/waterfall_props.rs. |
contracts/vault-interface |
Done | The ERC-4626 subset, plus asset and balance_of. No tests of its own. |
contracts/mock-vault |
Done | Test double, admin-set signed yield. 21 tests. |
contracts/epoch-manager |
Done | Epoch lifecycle, deposits, settlement, claims. 18 tests. |
contracts/blend-pool-interface |
Done | The Blend pool subset the adapter calls. Shapes checked against the deployed testnet pool by a successful deployment and epoch. |
contracts/blend-pool-mock |
Done | Pool test double. 12 tests. Never deployed. |
contracts/blend-adapter |
Testnet, unaudited | 23 tests against the double; deployed against the real testnet Blend pool on 2026-11-05 and settled one 300-second epoch. That deployment predates refresh(). No audit. |
| Cross-contract tests | Done | 30 tests over manager + vault + a real token. |
| CI | Done | fmt, clippy, test, wasm build, spec consistency, audit. |
| Testnet deployment | Done | Two mock-vault epochs and one Blend-adapter epoch settled and checked against the spec. |
docs/architecture.md, docs/risks.md |
Done | Data flow and authorisation; working risk register. |
| TTL audit for per-user positions (R9) | Planned | Open. Issue #10. |
| External audit | Planned | Not requested. Issue #6. |
strata-app SDK + dashboard |
In progress | Separate repo, not created yet. |
| Mainnet support | Out of scope | Deliberately. This project will not do it. |
Nothing in this table is placeholder code presented as finished. "Planned" and "not started" are stated as they are.
167 tests, all passing. Counted by cargo test --all --locked, one #[test]
per case:
| Where | Tests | What it proves |
|---|---|---|
contracts/waterfall/src/tests.rs |
19 | the spec's worked examples and boundaries, by name |
contracts/mock-vault/src/test.rs |
21 | the yield model, so integration results mean something |
contracts/epoch-manager/src/test.rs |
21 | lifecycle transitions, bounds, who may do what |
tests/src/waterfall_props.rs |
16 | all five spec invariants as properties |
tests/src/integration.rs |
30 | a real manager, vault and SEP-41 token, end to end |
tests/src/settlement_ordering.rs |
8 | what transaction ordering around maturity is worth: final-ledger deposits, settlement timing, claim remainder |
contracts/blend-pool-mock/src/test.rs |
12 | the pool double rounds, clamps, gates and expires the way Blend does |
contracts/blend-adapter/src/test.rs |
26 | every adapter function and error, against that double, plus the deposit auth tree |
tests/src/blend_adapter_integration.rs |
4 | full epochs through the real manager with the adapter as vault |
tests/src/blend_adapter_props.rs |
4 | adapter accounting properties against the pool double |
tests/src/hostile_vault_test.rs |
6 | reentrancy and fault modes of a hostile vault |
contracts/vault-interface, contracts/blend-pool-interface |
0 | trait declarations, nothing to test |
Every settlement assertion in the integration tests is checked against
strata_waterfall::settle computed independently, so the contract's numbers are
never taken on trust. Four bugs that shipped and were caught by these tests are
recorded as [realised] in docs/risks.md, including a
pro-rata denominator that shrank on every claim and would have overpaid the senior
tranche.
Read CONTRIBUTING.md first, then pick something from the
open issues.
- Comment on an issue before you start, so only one person works on it at a time.
- The waterfall spec is frozen. Disagree with the math? Open a
specissue. Do not change the numbers in a pull request. - Every invariant gets a test. New arithmetic needs new property tests.
- No placeholder code presented as finished. Unfinished work is an open issue.
- Testnet only.
Complexity and Points are assigned in the Drips Wave dashboard when an issue is
added to a Program, not by a label in this repository. CONTRIBUTING.md explains
what the documentation actually says.
Security reports go through SECURITY.md, not a public issue.
| Role | Contact |
|---|---|
| Maintainer | sulaimonifeoluwa4-blip |
| Security contact | sulaimonifeoluwa4@gmail.com β see SECURITY.md |
| Community channel | https://t.me/+N9ZmMAjKnCpjZWI8 |
| Document | What is in it |
|---|---|
docs/waterfall-spec.md |
The settlement math and its five invariants. Source of truth. |
docs/glossary.md |
Plain-English definitions of the tranche, vault and Soroban terms used in these docs. |
docs/architecture.md |
Layout, data flow, storage, authorisation. |
docs/risks.md |
Risk register, including four realised bugs. |
docs/analysis/settlement-ordering.md |
Who may call each transition, what the ledger clock allows, and what ordering around maturity is worth. |
docs/deployment.md |
Testnet deployment, reproduction steps, known issues. |
docs/demo-runbook.md |
Script for recording a walkthrough demo. |
docs/maintaining.md |
Branch protection and release process. |
docs/release-notes-v0.1.0.md |
Draft release notes. Not tagged, not released. |
strata-appβ TypeScript SDK generated from these contract specs, pinned to a contract version, plus the dashboard. In progress; the repository does not exist yet.
This repository contains smart contract code. Review deployment steps carefully before using any live network or production asset. Keep private keys, RPC credentials and wallet secrets out of commits, issue comments and logs. Identities for the testnet scripts live in the Stellar CLI's own store, outside the repository.
Apache-2.0, matching soroban-sdk and Stellar's own tooling.
Thanks to everyone who has contributed.