Audits & analysis
Test coverage, fuzzing, invariants, and the static-analysis triage.
No professional audit yet. That is stated plainly wherever it matters, and Phase 3 of the cap roadmap is gated on one. Here is what stands in the meantime, all reproducible from the public repo.
Test suite
Over 230 tests across every contract in the repository, layered:
| Layer | What it proves |
|---|---|
| Unit tests | Every function, every custom error, constructor validation ladders |
| Fuzz tests | Roundtrip bound: redeem(mint(x)) returns each constituent within [required − nTokens wei, required]; users never profit from rounding; reward-dust bounds in the registry |
| Invariant tests | Full backing after any call sequence: balanceOf(vault) ≥ totalSupply × units[i] / 1e18 for all constituents |
| Attack mocks | Reentrancy on transfer hooks (all four entry combinations), fee-on-transfer / lying tokens rejected by balance-delta checks |
| Dedicated guarantees | Redeem succeeds while minting is paused and supply is above cap; guardian powers are exactly the documented list and nothing else |
| V2 platform suites | Policy ceilings unbypassable (cooldown/turnover/slippage), backing recomputed after every rebalance, distributor never double-pays and never bricks on a frozen wallet, TWAP fail-safe paths |
| Mainnet fork tests | Full mint→transfer→redeem cycles against the real Stock Token contracts; zap quote == execution in the same block against live Uniswap v4 pools |
Coverage, stated precisely rather than rounded up:
- The frozen core (
BasketToken) holds 100% line, statement, branch and function coverage, as it has since launch. - The platform contracts around it (registries, splitter, guardian, logo registry, deployer) hold 100% line coverage.
- The V2 layer is heavily but not totally covered by unit suites at the
time of writing (
BasketToken2≈95% lines, distributor ≈87%, oracle layer ≈69–100% per contract); the zap routers are exercised primarily by the mainnet-fork suites (quote == execution against live pools) rather than unit coverage.forge coveragereproduces these numbers.
Before the immutable V2 deploy, the V2 contracts additionally went through two adversarial internal design reviews; the blocking findings (constructor validation, oracle storage-layout verification) were fixed or verified before deployment.
Run everything yourself:
cd contracts && forge test
# include the fork suites against Robinhood Chain mainnet:
RH_RPC=https://rpc.mainnet.chain.robinhood.com forge testStatic analysis: full triage
Zero unexplained findings is the policy: every tool finding is either fixed or documented with its rationale.
Slither
Zero findings of any severity in BasketToken.sol except:
calls-loop(Low): mint/redeem/isFullyBacked call constituent ERC-20s in a loop. Accepted: inherent to an in-kind multi-token vault; the set is bounded (2–20) and fixed at deploy.cyclomatic-complexity(Informational): the constructor validation ladder. Accepted: a sequence of independent input checks.- Reported High (
incorrect-exp) and Medium (divide-before-multiply) sit inside OpenZeppelin'sMath.mulDiv. These are well-known false positives on the Remco Bloemen implementation (the^is an intentional XOR in the Newton–Raphson inverse). Unmodified, audited library code.
Aderyn
2 High, both triaged as false positives / intentional:
- "State change after external call" (factory/registry): the external
calls are to protocol-owned contracts wired at deployment, or the
deliberate balance-delta pattern; every flagged function is
nonReentrant. Accepted. - "Unsafe int cast" (
IUniswapV4.sol): truncating to the low 128 bits is the defined decoding of Uniswap v4'sBalanceDelta(two int128s packed in an int256). Accepted.
Low findings (require-in-loop, 10_000 bps literals, missing events on
internal accounting, and similar) are accepted with the same rationales; the
full report ships in the repo (contracts/aderyn-report.md).
Known spec deviations
One, documented and owner-approved: the constructor takes an extra
initialSupplyCap parameter (validated 0 < initialSupplyCap ≤ maxSupplyCap) instead of the deploy script calling setSupplyCap
post-deploy; that call is guardian-gated and the guardian is a Safe, not
the deploy key. Setting it in the constructor removes a transaction and the
window where the cap is unset.
Build note: via_ir = true is enabled because the 9-argument BasketToken
constructor call in BasketFactory exceeds legacy-codegen stack depth. The
full suite passes under via-IR.
Continuous checks
CI runs forge fmt --check, the full test suite, frontend
lint/typecheck/build and scripts typecheck on every push. Contract source is
verified on Blockscout for every deployment.