⚠
HONEST STATUS: This is a
demo deployment on Arbitrum One using play-money RUSD (faucet token). Contracts are
pre-audit — Sourcify-verified, 163/163 forge + 38/38 integration tests green, but no external audit yet. Current max bet is
3,125 RUSD/hand, bond-limited (design target at $300M TVL: $520k–600k). One V1 incident is fully disclosed in the
audit package. Credibility is the product — read the gaps before the claims.
🛡 RAIN CHANNELS — AUDIT PACKAGE
Everything an external security auditor needs, in one place: contracts in scope with verified source, system architecture, the invariants to try to break, the threat model, test & coverage summary, known incidents, and accepted risks. Prepared honestly — including the gaps.
⚠ PRE-AUDIT — NOT YET EXTERNALLY AUDITED
SOURCIFY EXACT-MATCH VERIFIED
FORGE 163/163 · INTEGRATION 38/38
DEMO STAKES (RUSD PLAY-MONEY)
1CONTRACTS IN SCOPE
Solidity 0.8.24 · Foundry · via_ir=true · ~1,470 raw lines (~1,100 SLoC) · protocol/src/channels/
| CONTRACT | SLOC | STATUS | SOURCIFY |
| ChannelManager.sol (V3) — simulator-parity limits, incl. reclaimStale | 635 | ACTIVE | exact_match ✅ |
| ChannelManager.sol (V2) | 635 | RETIRED | exact_match ✅ |
| ChannelManager.sol (V1, commit e7da239) | 601 | RETIRED / DRAINED | exact_match ✅ |
| HouseBond.sol | 116 | ACTIVE | exact_match ✅ |
| BlackjackRulesVerifier.sol | 330 | ACTIVE | exact_match ✅ |
| RouletteRulesVerifier.sol — European roulette rules (13 bet types, exact payouts, solvency check) | 296 | ACTIVE | exact_match ✅ |
| GameMuxVerifier.sol — action-range dispatcher (0–31 blackjack, 32–63 roulette) | 45 | ACTIVE | exact_match ✅ |
| IRulesVerifier.sol (interface) | 51 | IN SCOPE | — |
| MockEntropyLocal.sol (test-only) | 27 | OUT OF SCOPE | — |
External dependencies: OpenZeppelin (SafeERC20, ReentrancyGuard, Ownable), Pyth Entropy (provider callback), RUSD ERC-20 (6 decimals, standard).
Off-chain components (threat-model context, not in on-chain scope): services/house-node/ (TS house node: WAL, checkpointing, grace timers, reconciliation, recover_close) and app/channels-sdk.js (browser SDK: session keys, hash chains, EIP-712 signing, snapshot/restore).
2SYSTEM SUMMARY
FunFair-style state channels for casino games
Player deposits RUSD; the operator allocates house float. Hands are played off-chain via dual-signed EIP-712 ChannelStates (domain "RAIN ChannelManager" v1). Randomness = mutual commit-reveal hash chains (N=4096) + a Pyth Entropy session seed mixed per card: card_k = keccak(pRev_k, hRev_k, sessionSeed, channelId, k) % 52 (% 37 for roulette), house-reveals-first ordering.
On-chain surface: open (entropy seed request) · checkpoint (nonce floor) · cooperative close · forced close + challenge window + finalize · reveal verification (submitReveals/demandReveal) · fraud proof (proveBadTransition) · stale-channel recovery (reclaimStale, 30-day) · fee waterfall at close (1.25% of lost gross → burn 4000 / rebate 1700 / standby 500 / residual 6300 ppm).
Key parameters: 180 s player grace → forfeit of open hand's gross · house fault → hand void + comp max(2× gross, 5 RUSD) from HouseBond + equal penalty burned · tiered challenge windows (≤100,000 RUSD → 10 min; above → 24 h) · exposure caps (Σ allocations ≤ 10% vault float, single ≤ 1.5%, 8×maxUnitStake ≤ allocation) · HouseBond 7-day withdrawal delay, pending withdrawals remain slashable · funds always pay to playerOwner, never session keys.
3DEPLOYED ADDRESSES — ARBITRUM ONE (42161)
All active contracts verified on Sourcify (exact_match); links open Arbiscan
House node: wss://api.rainsandbox.xyz · health: api.rainsandbox.xyz/health · V3 constants: EXPOSURE_SINGLE_PPM 15,000 · EXPOSURE_TOTAL_PPM 100,000 · TIER_THRESHOLD 100k RUSD.
4KEY INVARIANTS — WHAT TO TRY TO BREAK
Breaking any one is a valid finding
I1 · CONSERVATIONFor any settling state: playerBal + houseBal + feeAccrued + grossAtRisk == deposit + allocation. No path may mint or destroy escrowed RUSD.
I2 · NONCE MONOTONICITYA checkpointed nonce is a hard floor — no close may settle on a lower nonce; a challenge must strictly increase the closing nonce.
I3 · DUAL SIGNATURE INTEGRITYEvery settlement-bearing state requires valid player-session AND house-session signatures bound to the channel's registered keys, chainId, and verifying contract. No cross-channel or cross-chain replay (fuzz-tested).
I4 · FUNDS DESTINATIONSPlayer payouts only to playerOwner (never session keys); house payouts only to operator; the fee waterfall split is exact and applied exactly once.
I5 · RECLAIMSTALE CONSERVATISMOnly after ≥30 days of zero on-chain activity; settles at the last on-chain checkpoint (or opening balances); never better for the caller than the last anchored state; permissionless; cannot grief an active channel.
I6 · FORFEIT / FAULT PATHSPlayer-forfeit only after grace, and only the open hand's gross; house-fault pays void + comp max(2× gross, 5 RUSD) from HouseBond + equal penalty burned — never from other channels' escrow.
I7 · HOUSEBOND7-day withdrawal delay; pending withdrawals remain slashable; slash pays compensation before burn when underfunded; only the manager may slash; setManager only owner.
I8 · ENTROPYOnly the Pyth Entropy contract may invoke _entropyCallback; voidOpen full-refund only in SeedPending after 1 h; exposure caps enforced at open.
I9 · REENTRANCYAll fund-moving functions are nonReentrant; RUSD is standard but assume-hostile analysis welcome.
I10 · RULES VERIFIERSproveBadTransition accepts only a dual-signed prev and an accused-signed illegal next; a legal transition must never be punishable; card/number derivation must be unbiased given one honest party.
I11 · ROULETTE / MUX (v5.0.0)(a) spin outcome keccak(...)%37 unbiased given one honest party; (b) _bet solvency guarantees _spin can never underflow houseBal; (c) mux dispatch never lets one game's payload validate under the other's rules; (d) node-enforced worst-case payout per spin ≤ 8×maxUnitStake.
5THREAT MODEL
😈 MALICIOUS PLAYER
- Stale-state forceClose → challenge window + checkpoint floor
- Mid-hand stall → 180 s grace → forfeit of open gross
- Fake reveals → on-chain hash-chain verification
- Session-key theft → irrelevant: funds only pay to playerOwner
🏠 MALICIOUS OPERATOR / HOUSE
- Stale-state close → player challenge
- Withholding reveals → demandReveal escalation
- Refusing to countersign → player forceClose from last dual-signed state
- Node death → reclaimStale after 30 d guarantees escrow release
👥 THIRD PARTIES
- Griefing via permissionless functions (
finalizeClose, reclaimStale, submitReveals) → must not worsen an honest party's outcome
🌐 INFRASTRUCTURE
- Arbitrum sequencer outage vs challenge windows → tiered 10 min / 24 h — accepted risk at demo stakes
- Pyth Entropy liveness → voidOpen full refund after 1 h
6TEST & COVERAGE SUMMARY
Test-to-code ratio ≈ 1.9:1 by lines
FORGE (UNIT + FUZZ)
163/163
INTEGRATION (ANVIL E2E)
38/38
MAINNET E2E + DISPUTE DRILL
✅ EXECUTED
FORGE COVERAGE %
N/A (tooling)
- Forge: bond mechanics, open/seed/void, exposure caps, cooperative close + waterfall exactness, conservation, stale-state challenge, checkpoint floor, forfeit, comp+burn, tiered windows, reveal chains, demandReveal, fraud proofs both directions, reclaimStale suite, fuzz conservation + cross-channel replay; + 17 roulette-verifier tests (every bet type win+loss, 60-split mapping, insolvency rejection, 256-run conservation fuzz) + 3 GameMux dispatch tests.
- Integration (house-node E2E on Anvil, 38): happy path, waterfall, stale-close attack, mid-hand stall forfeit, checkpoint floor, refresh/restore, abandoned-channel reconciliation, WAL-loss recover_close; roulette open/seed, multi-bet dual-sign, payout exactness vs independent recompute, 8× exposure rejection, snapshot→restore→spin→close, blackjack regression on the same node.
- Mainnet: blackjack open/close + real forced-close dispute drill with a full 10-min window; roulette 6/6 E2E with real Pyth seed over production WSS (spins ~470 ms, exact on-chain payouts).
- Honest gap:
forge coverage is unobtainable — via_ir + coverage instrumentation hits "stack too deep" even with --ir-minimum. Line-coverage numbers unavailable.
7INCIDENT LOG
Full disclosure — what broke, what it cost, how it was fixed. Details in DEPLOYMENT.md.
✅ Abandoned-escrow recovery (v4.0.1)v4.0.0 kept session material in memory only — page refresh abandoned open channels. 4 channels (2,400 RUSD) recovered via operator cooperativeClose from WAL states. Fix: localStorage snapshot/restore, auto-resume, recover_close, 30-min reconciliation sweep.
⚠ Stuck V1 channel 0xdd64aa43… — 2,500 RUSD locked permanentlyOpened mid-maintenance; WAL wiped by redeploys, no dual-signed state on either side. V1 has no recovery path by design (re-audited: all settle paths need dual signatures). Player's 500 RUSD deposit refunded from treasury; 2,000 RUSD house allocation written off. This failure mode is exactly why reclaimStale exists from V2 on.
✅ House-node durability (v4.1.0)Bar: a redeploy mid-session must never destroy the only copy of a signed state. Three layers now: per-hand on-chain checkpoints (~130k gas, default ON), checkpoint-on-disconnect, SIGTERM flush — plus client snapshots and reclaimStale as the durable floor.
✅ Zombie-session reconnect loop (v4.2.1)Idle-tab channels closed by reconciliation left clients in an endless "Reconnecting…" loop. Fix: typed unknown_channel errors, on-chain state check, self-healing reopen. Funds were never at risk — UX bug, disclosed anyway.
8ACCEPTED RISKS & KNOWN LIMITATIONS
Auditors: please do not re-report these — known and accepted for v1 / demo stakes
- Demo/play-money deployment (RUSD faucet token). The 24 h tier window < Arbitrum force-inclusion delay is accepted at these stakes (spec §4.4).
- Single foundation-run house node; HA / watchtower / HSM out of scope for v1.
- House-node WAL on ephemeral
/tmp — mitigated in v4.1.0 by per-hand checkpoints, disconnect checkpoints, SIGTERM flush, client snapshots + recover_close. On-chain checkpoint + reclaimStale is the durable floor.
- Statistical fairness is transparency-only in v1 (no on-chain statistical slashing).
proveBadReveal intentionally subsumed by submitReveals chain verification.
- Per-hand checkpoints put hand-level P&L cadence on-chain (privacy = transparency tradeoff, fine for demo).
- If the operator wallet runs dry of ETH, the node degrades to WAL-only durability (alerts on checkpoint failure).
- Current max bet is bond-limited at 3,125 RUSD — not the float-derived design target. Deliberate clamp ordering so opens never revert on-chain.
- V1 ChannelManager is retired with one stranded channel documented above. Nothing should ever point at V1 again.
- Roulette VRF-fallback mode lacks split/street/six-line bet types (legacy contract); UI blocks them with an explanation.
9AUDIT-READINESS CHECKLIST
- Frozen scope defined (~1,100 SLoC, 6 contracts + interface)
- All active contracts verified on Sourcify (exact_match, chain 42161)
- Written audit scope: invariants, threat model, accepted risks
- Design spec (rain-channels-spec-v1) + full deployment/incident log
- 163 forge + 38 integration tests green; fuzz on conservation + replay
- Mainnet dispute drill executed with real challenge window
- No TODO/FIXME markers in channels contracts
- External audit engagement (est. $60–150k for ~1,600 new SLoC)
- Arbiscan API verification (needs ETHERSCAN_API_KEY; Sourcify metadata renders regardless)
- Line coverage % (blocked by via_ir tooling — documented)
- HA house node + watchtower + HSM (required before real-value stakes)
10AUDIT DOCUMENTS — DOWNLOAD THE FULL PACKAGE
🛡
audit-scope.md
The formal scope: contracts + SLoC, invariants, threat model, accepted risks, test summary, addresses. Start here.
📐
rain-channels-spec-v1.md
Full design specification: state-channel protocol, commit-reveal randomness, dispute game, economics.
📜
DEPLOYMENT.md
Complete deployment + incident log: every version, every tx, every recovery, honest caveats per release.
🎲
randomness-landscape.md
Research: how Stake, iGaming, Pyth, drand and FunFair-style channels handle randomness.
📄
WHITEPAPER-v0.9.md
The protocol extension paper (also as
PDF).
✅
Sourcify — ChannelManager V3
Exact-match verified source for the active ChannelManager, straight from the Sourcify repository.
Contact for audit engagement: via the dApp at rainsandbox.xyz or the operator address in the deployment log. Suggested firms & budget rationale in spec §7.3.