Docs v2 · testnet

Reference · Threat model

Adversaries, defenses, and what's still open

Every adversary we model, how the code defends against it, and an honest status. The long form — with test names for every fix — is docs/threat-model.md in the repository.

Adversaries modeled

20

Fixed / mitigated

16

each with regression tests

Partial

3

trust assumptions stated

Open / deferred

1

MEV (out of scope)

The adversary table

#

1

Threat

One malicious or faulty resolver

Defense

Quorum needs quorumThreshold byte-identical answers; one resolver can't propose alone.

Status

mitigated

#

2

Threat

Colluding resolver majority

Defense

Anyone can dispute; committees are drawn from the whole pool; Tier 2 then VOID, never a token vote. Residual: if the colluders ARE the pool they also sit on committees; authorization is owner-gated; flat bonds, no stake.

Status

partial

#

3

Threat

Late or stale observation

Defense

Rejected after observationDeadline; price-at abstains on rounds older than maxStaleness.

Status

fixed

#

4

Threat

Dispute spam / griefing

Defense

A wrong disputer loses the dispute bond; one dispute case per event. Residual: bonds are flat, not scaled to stakes.

Status

mitigated

#

5

Threat

Event Bus bypass by a consumer

Defense

Consumers hold only Settlement / IEventBus — enforced by a guard test per consumer.

Status

mitigated

#

6

Threat

Composite griefing (unknown operands, huge or cyclic graphs, dead operands)

Defense

Operands must exist; ≤10 children, depth ≤8; cycles impossible (proof below); voided operands propagate.

Status

mitigated

#

7

Threat

Non-deterministic composition

Defense

Uses committed finalizedAt / occurredAt, never block time at resolution; result cached forever.

Status

mitigated

#

8

Threat

Front-running market creation / settlement (MEV)

Defense

Out of MVP scope.

Status

deferred

#

9

Threat

Careless outcome decoding

Defense

bool stays the first ABI word in v2; every decode site audited; Registry rejects malformed v2 payloads.

Status

fixed

#

10

Threat

Committee-selection manipulation

Defense

Commit-reveal among resolvers: committee drawn from the XOR of revealed salts; withholders excluded. Residual: last revealer can drop themselves; block-data fallback only if nobody reveals.

Status

mitigated

#

11

Threat

Losers exit after the outcome is public

Defense

tradingClosesAt + 'outcome known' checks on every deposit/withdraw/trade.

Status

fixed

#

12

Threat

Voided event locks market funds

Defense

getAvailability returns Voided; Market refunds; range markets pay 1/N.

Status

fixed

#

13

Threat

Observations before the event happens

Defense

Rejected before openTimestamp; resolvers also wait.

Status

fixed

#

14

Threat

Reverting recipient freezes a dispute

Defense

Pull payments (pendingWithdrawals + withdraw()).

Status

fixed

#

15

Threat

Spoofed position ledger

Defense

Only the wired Market can record positions.

Status

fixed

#

16

Threat

Sequencer / timestamp trust

Defense

Windows are minutes-to-hours; no sequencer-uptime feed on Robinhood Chain, so price-at relies on maxStaleness.

Status

partial

#

17

Threat

Wrong or compromised source data

Defense

Dispute ladder is the backstop; raw evidence served at /evidence/:hash.

Status

partial

#

18

Threat

LMSR market-maker loss

Defense

Bounded at b·ln N (the price of liquidity); reserve ≥ C(q) re-checked every trade; no trading on known outcomes.

Status

by design

#

19

Threat

Ladder manipulation / inconsistency

Defense

Each boundary is disputable; an inconsistent ladder voids the market (1/N) instead of picking a range.

Status

mitigated

#

20

Threat

Fee attribution games

Defense

depositFees only adds rewards; only correct resolvers / voters are paid; insurance top-up capped at one third.

Status

mitigated

Committee randomness: commit-reveal

On Robinhood Chain (Arbitrum Orbit) prevrandao is the constant 1 and blockhash is weak, and Chainlink VRF isn't available — so committees aren't drawn from block data. When the pool is larger than the committee, resolvers commit to secret salts, reveal them, and the draw uses their XOR: unpredictable unless every revealer colludes. A committer who withholds is excluded, so the most anyone can do is remove themselves.

Cycle-impossibility proof

A composite's ID is keccak256(op, canonicalOperands, window) — a hash of its own definition, including its operand list. For a composite to reference itself, its operands would have to contain its own ID before that ID exists, which requires inverting keccak256. And EventComposer requires every operand to already exist in storage, so every edge points to a node created strictly earlier. No cycle of any length can be built — not bounded, impossible.

Terminal-state propagation

A voided primitive used to leave every composite above it reporting “not yet resolved” forever. The rule now applied to every operator, in order:

Precedence

1

Rule

A decisive short-circuit wins regardless of siblings (AND / BEFORE / WITHIN on any False; OR on any True).

Precedence

2

Rule

Otherwise a genuinely pending sibling keeps the composite Unresolved — normal openness, not a stall.

Precedence

3

Rule

Otherwise a Voided (or Expired) sibling makes the composite Voided.

Precedence

4

Rule

Otherwise every operand is decided and the normal comparison applies.

Out of scope for the MVP

Cross-chain events, ZK proofs, a permissionless resolver marketplace with staking tokens, MEV protection, and full stake-weighted / VRF-selected arbitration. The bonded committee ladder is real progress toward the last one — not the final design.