Skip to content

Latest commit

 

History

History
128 lines (104 loc) · 7.02 KB

File metadata and controls

128 lines (104 loc) · 7.02 KB

Security Notes — read before trusting Rota with anything

Rota is an MVP running on Arc testnet only. It has not been audited. This file honestly lists the known limitations and design trade-offs.

Smart-contract limitations

1. RANDOM_ORDER uses commit–reveal, not a VRF

RANDOM_ORDER derives its permutation from keccak256(secret, blockhash(fullBlock), address(this), members), where secret is committed by the organizer at creation (seedCommit = keccak256(secret)) and revealed via activateWithSeed(). This splits control so neither side can steer it:

  • The organizer fixes secret before any member joins, so they cannot aim it at a particular ordering.
  • blockhash(fullBlock) is fixed when the last member joins and is unknown at commit time. A validator can grind it, but without secret they cannot compute the permutation it produces — the grinding is blind.

On Arc, block.prevrandao always returns 0 (see docs/ARC_NOTES.md), so there is no in-protocol randomness beyond blockhash, and no VRF is available.

Residual risk. At reveal time the organizer can compute the resulting order and choose to withhold, letting the circle fall back to join order after REVEAL_WINDOW (1 day). That is a one-bit choice between two orders, neither of which they picked — inherent to any reveal scheme without a VRF. A real VRF would remove it.

Liveness. An organizer who loses or withholds the secret cannot hold the circle hostage: once REVEAL_WINDOW has elapsed after the circle fills, anyone may call activate() and the circle starts in join order.

Operational caveat. The secret must survive between two transactions, and the web app keeps it in localStorage keyed by the circle address. Clearing site data, or activating from a different browser or device, loses it — the circle still starts, but in join order rather than shuffled.

2. Collateral can under-cover defaults

Collateral is collateralBps of one contribution. A member who defaults in more rounds than their collateral covers produces a shortfall: that round's recipient receives a smaller pot. The shortfall is only made whole if the defaulter later calls cureDefault(). Organizers should choose collateralBps (up to 100%) with this in mind; multi-round collateral is future work.

3. Late settlement can eat into the next round

Rounds are schedule-anchored. If nobody calls settleRound() long after a deadline, the next round's contribution (and bid) window may be partially or fully elapsed when it opens, potentially causing cascading defaults. Any member, keeper, or bot can settle — the UI surfaces "settle available" prominently — but there is no protocol-run keeper yet.

4. AutoPay allowance UX risks

AutoPay is non-custodial and bounded: the contract can pull only the exact contributionAmount, once per round, only while the member is opted in and the round is open. However the ERC-20 allowance backing it is a standard approval: if the user approves "unlimited" and the circle contract were somehow compromised, the allowance is exposed. Rota defaults to exact-amount approvals and treats unlimited approval as an advanced option. Opting out does not revoke the ERC-20 allowance — revoke it in your wallet if desired.

5. Organizer powers

The organizer controls the allowlist of invite-only circles/pots and may cancel any not-yet-activated circle — including a full one racing against activate(). They have no access to escrowed funds. givingRecipient is fixed at creation — verify it before joining a circle with a giving cut.

6. Dust

Bid-discount division dust goes to the round's recipient; fallback-distribution and final-round dust (micro-USDC scale) can remain in the contract permanently. GoalPot haircut shares round down against the unlock snapshot, so a few micro-USDC may remain there too.

7. Token assumptions

Contracts assume a standard, non-fee-on-transfer, non-rebasing 6-decimal ERC-20 (USDC). Arc's USDC ERC-20 interface satisfies this. Do not deploy circles over exotic tokens.

USDC can blacklist addresses. Settlement and withdrawals are hardened against this: if a round recipient or givingRecipient cannot receive tokens, settleRound() credits the amount to their withdrawable dividend balance (event PayoutDeferred) instead of reverting, and GoalPot defers the giving cut to pendingGiving (retryable by anyone via flushGiving()). A frozen address can therefore never freeze other members' funds; only its own.

8. Reputation scores are farmable

The factory is permissionless and accepts any ERC-20, so an attacker can run circles/pots among their own wallets (even with a worthless token, or with USDC cycling between their own addresses and 1-second rounds) to inflate their Credit Passport at roughly gas cost. GoalPot credits at most one contribution per member per pot (dust-deposit spam is capped), but completions remain farmable. Treat the score as a within-community signal — verify the circles behind a score (token, members, amounts, durations are all on-chain) before extending real trust. Sybil resistance (token allowlist, stake- or time-weighted scoring) is future work.

9. Early exit vs. giving cut

emergencyWithdraw() is unavailable once a pot is unlockable (target reached or deadline passed), so the giving cut cannot be dodged at the finish line. Before that point, an exit pays the haircut instead of the giving cut; if an organizer sets givingBps higher than earlyExitHaircutBps, exiting early remains cheaper than staying. Choose earlyExitHaircutBps >= givingBps if that matters.

The haircut pool is paid out only by withdraw(), pro-rata against the deposit total snapshotted at unlock. An exit that empties the pot therefore leaves nobody to compensate, so it takes no haircut and sweeps the pool left by earlier exits — the last member standing is exactly who the pool is for. Without this the pool would be locked in the contract forever (the snapshot, and every remaining claim, would be zero).

Frontend limitations

  • FX rates are indicative. Local-currency figures come from a free client-side API, cached up to 12 h, and are never used in on-chain math. The exact USDC amount is always shown before signing.
  • Invite links are not access control. Anyone with the link can join a non-invite-only circle; use invite-only + allowlist for closed groups.
  • The "verify ownership" badge on the Credit Passport is a client-side signature check for in-person/screen-share verification, not a server-attested claim.

Regulatory

Rota has no KYC/AML layer and no licensing. Pooled-savings and rotating-credit products are regulated financial activity in many jurisdictions (e.g. chit funds are licensed in India; deposit-taking rules may apply in Bangladesh, the UK, and the US; money-transmission rules may apply to cross-border circles). This project must stay on testnet until reviewed per target market. Nothing here is an offer of financial services.

Reporting

This is a demo codebase; open an issue in the repository for any vulnerability found.