Robinwood StocksDocs
Security & Risk

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:

LayerWhat it proves
Unit testsEvery function, every custom error, constructor validation ladders
Fuzz testsRoundtrip bound: redeem(mint(x)) returns each constituent within [required − nTokens wei, required]; users never profit from rounding; reward-dust bounds in the registry
Invariant testsFull backing after any call sequence: balanceOf(vault) ≥ totalSupply × units[i] / 1e18 for all constituents
Attack mocksReentrancy on transfer hooks (all four entry combinations), fee-on-transfer / lying tokens rejected by balance-delta checks
Dedicated guaranteesRedeem succeeds while minting is paused and supply is above cap; guardian powers are exactly the documented list and nothing else
V2 platform suitesPolicy 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 testsFull 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 coverage reproduces 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 test

Static 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's Math.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's BalanceDelta (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.

On this page