Bias contracts
Perpetual futures on Robinhood Chain (chain id 4663, Arbitrum Orbit). Long or short stocks, ETH, BTC and chain tokens
with USDG collateral. The project's treasury wallet is the counterparty to every position: it receives all fees and
trader losses, and funds a per-position reserve that the vault escrows while the position is open. $BIAS launches on
Pons; its 5% creator tax on buys and sells goes to the custody wallet, which tops up the treasury (LAUNCH.md).
Status: unaudited. 190 tests + deep campaign passing; internal adversarial review (9 findings) fixed with regression
tests; full deploy → trade → handoff rehearsed on a mainnet fork (2026-09-15); coin risk system (2026-09-16) rehearsed for
deploy, configure and permissionless listing on a fork.
Read RISK.md (manipulation model) and RUNBOOK.md (deploy sequence) before touching code.
Layout#
src/
PerpVault.sol escrow (collateral + treasury reserve), positions, funding/borrow, liquidation, fees -> treasury
risk layer: liquidity-scaled OI caps, net-skew caps, skew impact fee, payout circuit breaker,
guardian close-only (never blocks exits), collateral top-ups, proof-of-backing view
OracleRouter.sol Chainlink class (staleness, NYSE hours with on-chain DST, sequencer hook) and TWAP class
(pool TWAP + median of per-signer slots, minSigners, replay-proof, divergence breaker, spot guard)
UniV4TwapSource.sol keeper-recorded tick + liquidity accumulator for a Uniswap v4 pool quoted in ETH, WETH or USDG:
twap() (binary search), spot(), depthUsd() / depthState() (minimum liquidity over the window,
live liquidity), recorder registry
RiskManager.sol liquidity tiers (wallet cap, side cap, leverage), tier ceiling, liquidity pull, warming, group
daily budget (hourly buckets), dead-coin clock (poke), delisting
MarketFactory.sol permissionless coin listing by pool key: checks, CREATE2 source, registration in oracle / vault /
risk manager, listing fee, active cap, token denylist, hook allowlist; TwapSourceDeployer
PositionRouter.sol two-step request -> keeper execute; failed opens refund atomically; user-only decrease cancel;
emergencyDecrease after timeout; bounded delays; min collateral; guardian removes keepers
script/
Deploy.s.sol RUNBOOK step 3: core contracts, guardians, executors, signers -> deployments/<chainid>.json
Configure.s.sol RUNBOOK step 4: launch markets with verified Chainlink feeds + risk presets (PARAMS.md)
SetMarketHours.s.sol NYSE holidays / early closes 2026-2027
DeployTwapSource.s.sol one UniV4TwapSource per chain-token market (graduated Pons pool by default)
Handoff.s.sol RUNBOOK step 7: ownership -> TimelockController (48h) run by a Safe; deployer keeps nothing
GovernanceHandoff.sol the handoff sequence, shared with test/Governance.t.sol
test/
PerpVault.t.sol escrow, PnL, profit cap, partial close, liquidation, OI caps, leverage, kill switch, funding
Risk.t.sol depth caps, skew caps, impact fee (exact), payout breaker, close-only, bounty floor, views
Router.t.sol refund-on-failure, emergency exit gating, keeper cannot cancel exits, top-ups in close-only
OracleRouter.t.sol Chainlink + TWAP classes, hours/DST, signer slots, replay, minSigners, spot guard, sequencer
UniV4TwapSource.t.sol accumulator, rounding, ring buffer, freshness
Governance.t.sol handoff end state, 48h delay, cancel, guardian restrict-only
Fuzz.t.sol liquidation threshold vs independent mirror, 24-step price paths, 5-year funding
Invariants.t.sol escrow exact, conservation, totals, leverage/OI after every op, view == liquidation
RiskInvariants.t.sol the risk layer under moving depth, close-only toggles, breaker trips: exits never blocked
Attack.t.sol pump attacks through the real oracle + vault on a simulated taxed pool (fuzzed, RISK.md §3)
Review_*.t.sol regression tests for review findings F1-F9 (RISK.md §5)
ForkOracle.t.sol read-only mainnet fork check of the TWAP source against a live Pons pool
CoinBase.sol full coin stack on simulated v4 pools (factory-listed), pump / dump helpers, keepers, Sybils
Tiers.t.sol tiers, wallet caps (combined), leverage, floor, pull, warming, tier ceiling, keeper-optional
GroupBudget.t.sol daily budget meter (exact model fuzz), gate, netting, reserve cap
PricePaths.t.sol worst-of price on open / close / emergencyDecrease / settle, neutral liquidation mark
Settlement.t.sol dead-coin clock, gaps, recovery, delist grace, stale TWAP / no signers, bounty
QuoteSource.t.sol USDG / WETH / ETH quotes both orderings, precision fuzz, binary search == linear walk
Factory.t.sol listing checks, CREATE2, registration, spam limits, lifecycle, guardian, denylist, hook fee
AttackUntaxed.t.sol pump / dump / pump-then-short on 0.3% and 1% pools at $14k / $35k / $175k with Sybils; 3-day
multi-coin run against the daily bound
CoinInvariants.t.sol stateful: tiers, wallet caps, group reserve, meter bound, any-24h loss bound, conservation
CloseFee.t.sol close fee on losing partial closes (known issue 1 fixed)
GasReport.t.sol call shapes for the gas table (run with --isolate)
ForkListing.t.sol mainnet fork: list PONS/USDG and FRONG/ETH through the factory, trade PONS (FORK_LISTING=1)
keeper/ off-chain services (executor, liquidator, pricer, recorder, funding, watchdog); keeper/README.md
fetch-feeds.js discovers + verifies Chainlink feeds on Robinhood Chain -> keeper/feeds.json
Setup#
export PATH="$HOME/.foundry/bin:$HOME/.local/node/bin:$PATH"
forge build
forge test # ~2s
FOUNDRY_PROFILE=deep forge test # 5000 fuzz runs, 256x128 invariants (~30s)
forge test --match-contract ForkOracle --fork-url https://rpc.mainnet.chain.robinhood.com -vv
forge test --match-test test_ponsConfig_reportMargins -vv # attack economics table in RISK.md
Session log#
Session 1: tests pass. ✅ 2026-09-08. USD 1e30 ↔ USDG conversion helpers, stack-depth splits, accrual-aware
isLiquidatable, invariant and fuzz suites.
Session 2: oracle. ✅ 2026-09-08, ORACLE.md. On-chain DST, holidays, UniV4TwapSource for v4 pools (no v3 on this
chain), staleness/divergence policy, ArbSys L2 block numbers.
Session 3: token + tax. ✅ 2026-09-08, LAUNCH.md. Token on Pons with 5% creator tax; treasury wallet is the sole
counterparty (escrow model, no LP shares).
Session 4: risk layer, governance, keeper. ✅ 2026-09-15.
- Liquidity-scaled OI caps from
UniV4TwapSource.depthUsd()(minimum liquidity over 30 min, fails closed), net-skew caps, quadratic skew impact fee, payout circuit breaker (now a 1h pause of opens, see 4b). Manipulation bound derived and fuzzed:oiDepthBps ≤ 0.4 × per-leg friction(RISK.md,test/Attack.t.sol). - Oracle: per-signer slots (one key cannot fill the median), strictly increasing timestamps per signer (fixed a replay
of any signature from the last 120s),
minSigners, spot guard (blocks "pump then open", prices "release then close" at spot), sequencer uptime hook, guardian signer removal. - Router: failed opens refund in the same transaction (no stranded collateral), only users cancel their closes,
emergencyDecreasegated on an expired request (was ungated), bounded delays, min collateral, collateral top-ups that work in close-only. - Vault: guardian close-only (per market and global; restrict-only), fixed duplicate
marketIdson re-enable (double-counted aggregate OI), flat liquidation bounty even when underwater,backing(),capacity(),quoteImpactFee(),fundingRatePerHour()views. - Governance:
Handoff.s.sol→ TimelockController 48h + Safe, deployer renounces everything; proven in tests. - Chainlink feeds found and verified on-chain (6 of 8 launch markets; no GLD, AMC or sequencer feed). Spreads raised to 30 bps (feeds lag up to 0.5%); staleness 25h after the fork rehearsal showed SPY stale for a whole session at 6h.
- Mainnet-fork rehearsal: Deploy → Configure → SetMarketHours → fund → open/close a real ETH position via the keeper path (round trip $3.80 on $500 = fees + spreads; vault back to 0) → Handoff → deployer calls revert.
- Keeper rewritten as separate services with ABIs synced from
out/(the v0 keeper's hand-written event ABIs did not match the contracts, so its liquidation scan never saw a position).
Session 4b: adversarial review. ✅ 2026-09-15. Nine findings proven with tests, all fixed (RISK.md §5): withdrawal
margin counts unrealized loss (critical), self-hedge skew netting, hedged breaker trips and 1h breaker cooldown, exits
survive missing signers or TWAP history, two-signer agreement band, pre-armed emergency tickets closed, spot guard off
the mark, 15s record spacing, expired opens refund. Keeper feedback also applied: out-of-gas failures revert instead of
refunding, enabled = false stops opens only, optional pull-paid execution fee, keepers clear decrease requests for
closed positions, KeeperPricePosted carries the signer.
Session 4c: small-size launch caps. ✅ 2026-09-15. An on-chain scan showed depthUsd overstating concentrated
pools 3-15x (SPY/USDG pool $409M virtual vs ~$51M real), unknown hooks, and v4 pools that are not the dominant venue.
For a small launch these are bounded with fixed caps instead of new contract code: every market gets a small fixed
per-side OI cap and net-skew cap, the vault enforces min(fixed, depth-derived) (test/LaunchCaps.t.sol), aggregate
OI $60k, and TWAP listings are limited to native-ETH-quoted pools with no hook or the Pons MemeHook plus a written
listing rule (LAUNCH.md). No src/ change (INTERFACE_CHANGES.md). On-chain conservative depth stays open
(RISK.md §7).
Session 4d: long / short any coin. ✅ 2026-09-16. Untaxed coins (0.3% to 1% LP fee, no tax) cannot be made
unprofitable to manipulate with any tradeable cap, so they get bounded worst cases instead (RISK.md section 8):
RiskManager liquidity tiers (Lite $14k to Large $700k: $300 to $2k per wallet, $750 to $5k per side, 3x / 5x), combined
per-wallet caps (majors $5k), 100% max profit per coin market, a coin-group daily budget with hourly buckets and a group
reserve cap (any 24h < $10,500), liquidity-pull and warming refusals, a 24h tier ceiling against depth pumps, dead-coin
settlement after 3 days below the floor, 1h TWAP with binary search, USDG and WETH quoted pools, and MarketFactory for
permissionless listing (keeper-optional, Lite without signers). Close fee fix, totalOi counter. Attack suite, invariants
and a mainnet fork test on PONS/USDG. Interface changes: INTERFACE_CHANGES.md.
Session 5: indexer + front-end wiring. Events → Postgres → API for positions, funding history, treasury PnL, and a
public backing page (vault.backing(), per-market capacity()). Wire hoodperps-site to reads + router writes, and
show quoteImpactFee and the acceptable price before signing.
Session 6: audit prep. Freeze src/, write the spec from RISK.md, run slither/aderyn, external audit. Caps stay at
launch values until the audit is closed.
Known simplifications (documented on purpose)#
- Reserve rule:
reserve = min(size × market.reserveBps, netCollateral × maxProfit)(global 900%, per-market override: coins 100%) pulled from the treasury on open; profit on a position is capped at its reserve. Every open position is fully backed by escrow. - Funding a trader receives is paid from that position's own reserve (the treasury's money), which lowers its max profit by the same amount. Exact pairwise funding accounting is a later item.
- Borrow-rate utilization is
totalOi / maxTotalOi(100% when uncapped). - The payout breaker uses net trader PnL with linear one-hour decay and a −cap floor on loss credit; it counts realized PnL only (closes and liquidations), netted per account against an open opposite position. A trip pauses opens for 1h.
- The group daily budget meters treasury payouts in non-negative hourly buckets (losses consume recent buckets, no banked credit). Borrow and funding paid by traders are not credited, so the meter over-counts losses slightly.
- Self-hedge netting is per account; colluding accounts are bounded by per-side OI caps and pay fees on both legs.
- The skew impact fee is charged on increases only and never rebated; balancing trades pay nothing.
depthUsdtreats in-range liquidity as full-range (correct for Pons graduation pools; overstates concentrated pools, so fixed caps bind at launch; see RISK.md §7).- No insurance fund: the treasury absorbs bad debt, bounded per position by the reserve.
emergencyDecreaseenforces the request's acceptable price and works from 180s to 1h+180s after the request, for a position that existed when the request was made. A user whose price was too tight queues a new request.
Launch parameters#
See PARAMS.md (values + rationale) and script/Configure.s.sol (applies them). Chainlink class at launch: ETH, BTC,
SPY, QQQ, NVDA, AAPL (GLD and AMC have no feed on this chain). TWAP class: the governance-listed coins in
script/Configure.s.sol (PONS, AI and BONER optional now that USDG pools price correctly), each with its own
UniV4TwapSource, plus any coin listed through MarketFactory.