Accounting drift
shares × price ≤ assetsSoroquake fuzzes Soroban contracts with thousands of multi-user transaction sequences, checks your invariants after every step, and hands back the shortest sequence that breaks one, as a Rust test.
Calls in a shrunk repro, our target for 9 in 10 failures
Default pull-request campaign, corpus replay included
Ready-made invariant packs for tokens, AMMs, lending, vaults and more
Drained from YieldBlox on Blend V2 in Feb 2026 by a multi-step attack
Engine figures are design targets for v1. The incident figure comes from public post-mortems.
Each handler is a plain Rust function that calls your contract: deposit, swap, liquidate, set a fee. Soroquake picks who calls it and with what.
Invariants read state and pass or fail. Solvency, supply equals balances, only the admin changes fees. Import packs for the standard ones.
Thousands of sequences across actors, ledger time, oracle moves and TTL expiry, guided by coverage, with every invariant checked after every step.
The failure shrinks to the fewest calls that still break it and lands in your repo as a test that stays red until you fix it.
Example: a vault rounding bug found and turned into a test.
shares × price ≤ assetsswap(a, a) changes nothingsupply == Σ balancesliquidate ⇒ health improvesonly admin sets the feeno panic near i128::MAXrestore ⇒ nothing lostinvariants hold after migrateNo borrower takes out more than their collateral is worth at a fair price
Documented by BlockSec, Halborn and Blockaid
A same-asset swap is a no-op; reserves never fall without an outflow
Waiting for a primary post-mortem
Every case ships with four parts, and each release must rediscover all of them.
cargo soroquake lab apply yieldblox --target my_poolRunning 12 invariants for 5 minutes
| supply_matches_balances | running… |
| only_admin_sets_fee | running… |
| solvent | running… |
#[test] fn soroquake_solvent_0x5eed() { let s = setup(&Env::default()); deposit(&mut s, ALICE, 3); accrue(&mut s, 1); withdraw(&mut s, ALICE, 1); assert!(solvent(&s).is_pass()); }
Add soroquake/action@v1 and every PR gets a check and a comment.
Sequences that reached new code are cached between runs, keyed by your contract's code hash.
SARIF upload puts broken invariants into GitHub code scanning next to the handler.
fuzz/failures/solvent-0x5eed.seq
withdraw pays more than a fair share
Shrinking and replayable failures
sep41Checking…ammChecking…vaultChecking…lendingChecking…accessChecking…ttlChecking…Invariant packs
mainnet snapshotledger 58,214,903fetching…fetching…fetching…fetching…Mainnet fork mode
Attack sequence
Ledger state
Exploit Replay Lab
Read how the lab worksNot yet. The repository holds the spec and a first scaffold. The core engine (handlers, invariants, shrinking) is the first milestone; fork mode, the Replay Lab and the GitHub Action follow.
Stellar's docs show how to hand-write a stateful fuzz harness for one contract. Soroquake is the reusable version: a handler and invariant API, multi-contract sequences, readable shrunk tests, a CI corpus, fork mode and ready-made packs.
They prove properties formally and are great for the few that matter most. Soroquake is the fast, everyday loop that runs on every pull request. Use both.
Fork mode reads a ledger snapshot taken with stellar snapshot create. Soroquake never signs or submits a transaction.
soroban-token-fuzzer covers SEP-41 tokens and yarobanza/soroban-fuzz is an early generic MVP. We credit both and focus on what neither has yet: multi-contract harnesses, coverage guidance, fork mode, incident replays and CI.
Nothing. Everything is Apache-2.0, runs on your machine or your CI, and sends no telemetry by default.