Security & Audit

How Rain Vaults is protected, who has reviewed it, what they found, and exactly what changed. We publish the uncomfortable parts too — a security page that only lists wins is marketing, not security.

Contract line: RainVault V4 (rev 4.1) Auditor: Composable Security Audit ID: RAIN-5e2b9934 Status: V4.1 fixes complete · awaiting final audit round · not yet deployed
Contents 1. Where we are today (read this first) 2. Audit timeline 3. What V4 changed — and why 4. Finding-by-finding fix map (all 28) 5. What the retest found that we missed 6. Deliberate design choices the auditor flagged 7. Verified against the live Rain protocol, not a mock 8. Testing methodology 9. What V4 guarantees — and what it does not 10. Currently deployed contracts (v1–v3) — risk notice 11. Source code & reports

1. Where we are today

28
Findings total (2 rounds)
4 + 1
Criticals found · all addressed
16 → 26
Fixed after retest → after rev 4.1
0
Open without a fix or a documented decision
Plain language. Composable Security audited our V3 contracts (Aug–Sep 2026) and found four Critical issues. We rewrote the vault as V4 and submitted it for a retest. The retest (16 Sep 2026) confirmed all four original Criticals fixed — and found one new Critical and one new High in V4 that our own testing did not catch, because we had tested against a simulated Rain market instead of the deployed one. Rev 4.1, published here, fixes both new findings and every remaining item, and was verified against all 552 live Rain pools on Arbitrum One. The auditor's verdict on V4 was "not production-ready; another full round recommended." We agree, and that round is scheduled. V4.1 will not be deployed until it passes.

2. Audit timeline

3. What V4 changed — and why

Every one of the four original Criticals, and eight of the twelve Highs, descended from a single design decision in V1–V3: the vault computed a NAV by marking open positions at the best resting bid on the Rain order book. On thin prediction-market books, that mark can be moved for the cost of one resting order — so deposits, withdrawals and fees were all priced off a number an attacker could set.

V4 principle: "Hyperliquid in execution, not in marking." The vault never marks a position. There is no NAV. Shares are only ever priced against realised cash, and the only way value moves between holders is through actual execution on Rain.
FlowV1–V3V4 / 4.1
DepositMint shares at NAV (manipulable)USDT waits in a pending pool; converts to shares only when the vault is flat (all cash), at cash-per-share. Cancel any time; auto-refund after 14 days. Pending funds can never be traded.
WithdrawPaid at NAV if cash available, else FIFO queue + 48h force-unwindExit by execution, same transaction: (1) resolved positions claimed to shared cash; (2) your pro-rata slice of every open position is merged (matched YES/NO pairs at face value) then sold; (3) the amount actually sold is measured as the share-balance delta; (4) anything unsold is reserved to you — still yours, sellable later or redeemable 1:1 at resolution. Payout = your share of cash before the sales + your own proceeds. Remaining holders' cash-per-share is provably unchanged.
FeesOn NAV, continuouslyOnly on realised cash at flat moments, above a high-water mark. Management fee crystallised before any pricing in withdraw.
LivenessAny market revert could freeze the vaultEvery market view or call the vault's liveness depends on goes through a guarded call. A broken market can make one position "not flat"; it can never block anyone's exit, force-unwind or vault close.
Force-unwindAfter 48h queue waitPermissionless after 30 days without a flat state; bound to a specific (market, option); caller sizes the sale (≤ 20% of free shares) and sets slippage.

4. Finding-by-finding fix map

All 28 findings across both rounds. Status reflects rev 4.1. "Design" means we made a deliberate, documented choice that differs from the auditor's recommendation — see §6.

IDSevFindingRev 4.1How
C01CriticalManager below 5% floor jams withdrawal queueFIXEDNo queue exists. Floor is a stock invariant (§M03/M04).
C02CriticalTrade key drains vault via self-registered fake marketFIXEDregisterMarket manager-only; must be a Rain-factory pool with USDT base token. Exact per-trade approval, reset to 0.
C03CriticalNAV manipulable via order-book markFIXEDNo mark exists.
C04CriticalNAV deflation lets manager take cashFIXEDCash never divided against a mark; manager exits by execution like everyone.
C05Critical · newRain pools without getClaimableAmount freeze the vaultFIXED 4.1All liveness-critical market reads are guarded staticcalls with conservative fallbacks. registerMarket probes the view and rejects pools lacking it. Verified: 144 of 552 live pools lack it (§7).
H01HighDust deposit resets lock-upFIXEDOnly deposits ≥1% of holding extend lock-up.
H02HighUnpriceable position marked 0FIXEDNo marking.
H03HighCancelled queue entries consume request capFIXEDNo queue.
H04HighDeposit 1:1 fallback when NAV = 0FIXEDConversion reverts if shares exist and cash is zero.
H05HighOversized market valuation overflows NAVFIXED 4.1No NAV. Additionally every market-reported quantity is clamped (MAX_REPORTED) so an absurd value reads as 0, not as a revert.
H06HighPrune drops live positionFIXEDPrune only when provably flat.
H07HighClose over zero-marked live positionsFIXEDcloseVault requires flat.
H08HighEscrow shares double-countedFIXEDNo NAV.
H09HighMatched YES/NO pairs sold leg-by-leg instead of mergedFIXED 4.1Exit merges matched pairs at face value first, on every market type, then sells the remainder.
H10HightradingModel() used as phase indicator (wrong after AMM → order-book)FIXED 4.1tradingModel removed from all routing. Verified on-chain: it stays 0 after optionEndTime on every AMM pool.
H11HighGross top-of-book overstates NAVFIXEDNo NAV.
H12HighDispute → both cranks fail; no permissionless exitFIXED 4.1 DESIGNClaim is guarded and never reverts the caller. During a dispute window an exiter receives a reservation (not cash) for that position — see §6.
H13High · newZero/partial order-book fill burns exiter's sharesFIXED 4.1Sales measured by share-balance delta, not by "did not revert". Unsold shares are reserved per (market, option, side, user); the vault's trading can never touch them; owner sells later or redeems at resolution.
M01MediumFactory key pulls grantor's USDTFIXEDSeed pulled from the key (msg.sender).
M02MediumForce-unwind gated on age only / no solvency triggerPARTIAL DESIGNGated on 30 days without flat and positions exist; caller-sized, slippage-protected. No solvency trigger by design — there is no NAV to be insolvent against. §6.
M03Medium5% floor diluted by depositsFIXED 4.1A deposit conversion that would push the manager below 5% reverts. Floor is now a hard stock invariant, fuzz-checked after every operation.
M04MediumSole-holder exemption on the floorFIXED 4.1Exemption removed. A manager who wants out entirely must closeVault() (requires flat).
M05MediumWrong approval unit for buy ordersFIXEDExact USDT approval per trade.
M06MediumLosing positions exhaust slotsFIXED32 slots; resolved/flat positions pruned.
M07MediumZero payout burns stakeFIXEDReverts unless a reservation was created.
L02LowCash read before fee accrual in withdrawFIXED 4.1Management fee crystallised first.
L03LowNo way to retire a marketFIXED 4.1retireMarket stops new spend; exit, cancel, merge, claim, force paths stay open forever.
L05LowFee clock reset on zero accrual via pokeFIXED 4.1Clock advances only when shares were actually minted.
R02Rec.Force-unwind by index is race-proneFIXED 4.1forceUnwind(idx, market, option, …) reverts StalePositionIndex on mismatch.

L01, L04, R01 and I-class informational items were confirmed fixed at retest and are omitted for brevity; the full list is in the reports (§11).

5. What the retest found that we missed — and the lesson

Both new findings share one root cause: our fuzz harness tested against a mock Rain market that behaved the way we assumed Rain behaves. Deployed Rain differs in two places that matter.

C05 — pools without getClaimableAmount

V4 called getClaimableAmount directly in the code path that decides whether a position is flat. 144 of the 552 pools created by the Rain factory (140 of them USDT) were deployed with an older facet set that does not have this function; the call reverts with FunctionNotFound. Register one of those pools, let an option resolve, and every entry point of the vault — including the escape routes — reverts forever. A regression versus V3, which had containment here. Our mock always implemented the function, so the fuzzer could not see it.

H13 — silent partial fills

Rain's sellOption on an empty or shallow order book returns successfully after selling nothing, or only what depth exists. V4 treated "did not revert" as "sold", burned the exiter's shares and paid only what came back. Our mock reverted on an empty book, so the fuzzer never produced that state.

What changed in how we test. Rev 4.1's mock was rebuilt from observed Rain behaviour: partial-fill-returns-success, legacy pools without the claim view, AMM order-book phase after optionEndTime, dispute windows, merge refused after close. Every retest finding now has a fuzz operation that exercises it, and the invariants that would have caught C05 and H13 (I10, I11) run after every operation. We also run a read-only probe against all live Rain pools before each release (§7).

6. Deliberate design choices the auditor flagged

Three items where we chose differently from the recommendation. We document them here so depositors can judge for themselves.

H12 · Exiting during a dispute

Recommendation: a permissionless path that pays exiters cash even while a Rain option is under dispute. Our choice: Rain refuses both claim and merge once a pool is closed, so no on-chain cash path exists — anyone promising one is pretending. Instead the exiter is never blocked: they receive their full share of every other position in cash, plus a reservation on the disputed one, redeemable the moment Rain settles. The vault cannot freeze; the exiter cannot be diluted; they wait exactly as long as Rain makes everyone wait.

M02 · No solvency trigger for force-unwind

Recommendation: trigger force-unwind when the vault is "insolvent". Our choice: V4 has no NAV, so there is no solvency metric to test — inventing one would reintroduce the manipulable mark that caused C03/C04. Force-unwind is instead gated on 30 days without a flat state, and the exit-by-execution path is available at all times regardless. We accept "partially fixed" on this item.

M03 · Hard manager floor caps AUM

Consequence we accept: because deposits that would dilute the manager below 5% now revert, a vault's capacity is ~20× the manager's own stake. A manager who wants more AUM must add more of their own capital. We consider this a feature — it is what "skin in the game" means — but it is a real product constraint and depositors should know it.

7. Verified against the live Rain protocol, not a mock

Before publishing rev 4.1 we ran a read-only probe from an Arbitrum One archive node against every pool created by the Rain factory 0x38B3Ba1ee001E6785224E31b3031ae96CA06C677:

552
Factory pools probed
144
Lack getClaimableAmount (C05)
552
Expose optionEndTime · DISPUTE_WINDOW
0
AMM pools whose tradingModel changes phase (H10)

Findings: the auditor's C05 count is exact; DISPUTE_WINDOW is 3,600 s on all pools; tradingModel() returns 0 on every AMM pool including those long past optionEndTime, confirming H10. The probe scripts (probe-live.js, probe-shape.js) ship with the contracts so the auditor can re-run them.

8. Testing methodology

Current numbers (rev 4.1): a 1,000-operation fuzz run is in progress at the time of publishing; the interrupted first run reached 200+ operations with 0 findings. This page will be updated with the completed figures and seeds. We do not quote numbers we have not finished producing.

9. What V4 guarantees — and what it does not

Guaranteed by the contract

Not guaranteed — please read

10. Currently deployed contracts (v1–v3) — risk notice

The vaults live on rainvaults.com today run the V2/V3 contracts that were the subject of the initial audit. They contain the NAV-marking design described in §3 and the findings marked "FIXED" in §4 are fixed in V4, not in these deployments. Live vaults are small and the team has no ability to upgrade them (they are immutable by design). Our recommendation until V4.1 is audited and deployed: do not deposit new capital into v1–v3 vaults, and managers of live vaults should flatten and allow depositors to exit. Existing depositors can withdraw at any time after lock-up.

11. Source code & reports

ItemLocation
V4.1 contracts, mocks, fuzz harness, probes, audit responsegithub.com/agentbase-prod/rainvaults-com · contracts/v4
Rain market interface compiled againstcontracts/IRainMarket.sol
Compile & verifycd contracts/v4 && npm i && node compile.js  (solc 0.8.26, viaIR, runs=1)
Audit reportsComposable Security — Initial (2026-09-01) and Retest (2026-09-16), ID RAIN-5e2b9934. Published here on completion of the final round.
Responsible disclosureFound something? Open an issue on the repository or contact the team via the Rain community. We will credit you.