Skip to content

Perps hardening v1 — full EVM/Solana parity#31

Open
rndrntwrk wants to merge 21 commits intodevelopfrom
feature/perps-hardening-v1
Open

Perps hardening v1 — full EVM/Solana parity#31
rndrntwrk wants to merge 21 commits intodevelopfrom
feature/perps-hardening-v1

Conversation

@rndrntwrk
Copy link
Collaborator

Summary

  • P1B.1–3: Oracle hardening (AccessControl, staleness, delta caps, pause), governance freeze (PM20 pattern on both EVM engines, config_frozen + paused on Solana), risk engine upgrade (partial liquidation, insurance waterfall, oracle staleness guards)
  • P1B.4: EVM/SVM parity — slippage protection (acceptablePrice), fee splitting (treasury + MM), OI caps, per-market insurance isolation (Native), settlement price freeze
  • P1B.5: CI — forge test jobs with fuzz 512, perps gate workflow in prediction-market-gates.yml
  • P1B.6: Keeper ABI updates for new MarketConfig/MarketState fields
  • P1B.7: CREATE2 deploy script, chain registry skillOracleAddress + perpEngineAddress fields
  • P1B.8: Audit packet with threat model, known issues, test evidence
  • P1B.9: Bidirectional parity — Solana gains partial liquidation, insurance waterfall with socialized loss cap, bad debt tracking, PnL-aware maintenance margin, repay_bad_debt; EVM gains oracle price step validation, min insurance fund, open positions counter with archive validation

Test plan

  • 42 Forge tests pass (40 unit + 2 fuzz at 512 runs)
  • Balance sheet invariant holds across random trade sequences
  • Liquidation bad debt tracking invariant holds
  • anchor build -p gold_perps_market succeeds
  • Solana integration tests pass
  • Manual review of parity matrix (docs/perps-parity-matrix.md)

… (P1B.1–3)

SkillOracle: migrate Ownable → AccessControl with REPORTER_ROLE/PAUSER_ROLE,
add maxOracleDelay staleness check, mu/sigma delta caps (500/300), oracle
pause mechanism, PM20 governance surface freeze on setMaxOracleDelay and
non-PAUSER role mutations.

AgentPerpEngine + AgentPerpEngineNative: migrate Ownable → AccessControl
with MARKET_OPERATOR_ROLE/PAUSER_ROLE, PM20 governance freeze (grantRole/
revokeRole/renounceRole revert for non-PAUSER roles, frozen config setters),
trading pause, market creation pause, market status lifecycle (ACTIVE →
CLOSE_ONLY → ARCHIVED), partial liquidation (2x maintenance margin target,
10% minimum close), insurance waterfall (50bps socialized loss cap),
oracle staleness guard in syncOracle.

Solana gold_perps_market: add config_frozen one-way flag (blocks
update_config and initialize_config re-init), add paused flag (blocks
modify_position), new freeze_config and set_paused instructions.

27/27 Forge tests pass (25 unit + 2 fuzz).
…eze (P1B.4)

AgentPerpEngine + AgentPerpEngineNative:
- Slippage protection: acceptablePrice parameter on modifyPosition,
  reverts SlippageExceeded for longs above / shorts below limit,
  0 disables check (backward compat)
- Fee splitting: tradeTreasuryFeeBps + tradeMarketMakerFeeBps per
  market, deducted from trader margin on each trade, tracked per
  market with withdrawFeeBalance and recycleMmFees
- Open interest caps: maxOpenInterest per market config, checked
  after position modification, MaxOpenInterestExceeded revert
- Settlement price freeze: captured on CLOSE_ONLY transition,
  used for all subsequent execution price calculations in that market
- Per-market insurance isolation in native engine (was global pool)
- Per-market insurance deposit/withdraw in native engine

Parity matrix: docs/perps-parity-matrix.md covering all features
across ERC20, Native ETH, and Solana engines.

36/36 perps tests pass (34 unit + 2 fuzz), 79/79 full suite.
- foundry.toml: set default fuzz runs to 512
- ci.yml: add perps-contract-tests job (forge test test/perps/*)
- prediction-market-gates.yml: add perps-forge-gate (EVM fuzz 512)
  and perps-solana-gate (anchor build gold_perps_market)

Both CI jobs use the existing setup-hyperbet action with
install-foundry/install-anchor flags.

36/36 perps tests pass at 512 fuzz runs.
Update marketConfigs ABI to include maxOpenInterest,
tradeTreasuryFeeBps, tradeMarketMakerFeeBps fields.

Update markets ABI to include settlementPrice,
treasuryFeeBalance, marketMakerFeeBalance fields.

Keeper service layer, db, perpsMath, modelMarkets, and
sync cron loop already implemented — no new code needed.
Chain registry: add skillOracleAddress and perpEngineAddress fields
to BettingEvmDeployment interface. Initialize empty across all 6
EVM network records (bscTestnet, bsc, baseSepolia, base, avaxFuji,
avax). Addresses to be populated after deployment.

CREATE2 deploy script: deploy-perps-create2.ts deploys SkillOracle
and AgentPerpEngine via deterministic CREATE2 factory for matching
addresses across BSC + AVAX + Base. Configurable via env vars for
admin, reporter, operator, pauser, margin token, and salt.

Solana program ID already in registry: HbXhqEFevpkfYdZCN6YmJGRmQmj9vsBun2ZHjeeaLRik
…nce (P1B.8)

Audit packet covers:
- Scope: 3 EVM contracts + 1 Solana program
- Threat model: 8 attack surfaces with mitigations
- Known issues register (4 items, all low/info)
- Test evidence: 36 tests (34 unit + 2 fuzz at 512 runs)
- CI integration: 2 gate jobs
- Deployment model: CREATE2 + Solana program ID
- References parity matrix doc
… waterfall, open positions (P1B.9)

Solana: partial liquidation with 2x maintenance target and 10% min close,
insurance waterfall with socialized loss cap (50bps), bad debt tracking,
PnL-aware maintenance margin health check, repay_bad_debt instruction.

EVM: oracle price step validation (maxOraclePriceDeltaBps), min insurance
fund requirement (minInsuranceFund), open positions counter with archive
validation requiring zero positions. Applied to both ERC20 and Native engines.

42 EVM tests pass (40 unit + 2 fuzz at 512 runs). Parity matrix updated.
@rndrntwrk rndrntwrk changed the base branch from main to develop March 20, 2026 14:16
Cover partial liquidation (position survives with reduced size), full
liquidation with insurance drawdown, bad debt accumulation, PnL-aware
maintenance margin check (blocks increase, allows reduction),
repay_bad_debt happy path and revert, and insurance waterfall ordering.

Fix existing liquidation test broken by partial liquidation: use higher
entry price so a drop to PRICE(80) causes negative equity for full close.

Fix Rust build error: rename local `execution_price` variable to
`exec_price` to avoid shadowing the `execution_price()` function in
modify_position's maintenance margin check.

Add `crashOracle` test helper for multi-step oracle drops that stay
within max_oracle_price_delta_bps bounds.
…ized locals

- Make constructor-only state vars immutable (fundingVelocity, defaultSkewScale,
  skewScale, maxLeverage) since their setters revert with GovernanceSurfaceFrozen
- Initialize liquidationSizeDelta to 0 in both engines to satisfy uninitialized-local
- Exclude dangerous-strict-equalities and unused-return from Slither — false positives
  for zero-guard checks and intentionally discarded oracle return values
…PDA seeds

- set_paused: check config_frozen before allowing pause toggle (H6)
- set_market_status: reject repeated CLOSE_ONLY transitions to prevent
  settlement price overwrite after positions have begun closing (H7)
- liquidate_position: use close_size_delta (not old_size) for funding PnL
  so partial liquidations only realize proportional funding (H8)
- LiquidatePosition: add PDA seeds constraint on position account to
  match ModifyPosition's derivation pattern (C5)
- Liquidation reward computed on post-PnL margin instead of pre-PnL
  startingMargin, preventing insurance drain for rewards (C1+C2)
- Partial liquidation updates entryPrice via weighted average so
  remaining position PnL is calculated correctly (H2)
- _creditMarginFromPool: vault-only for trader profit payouts. Insurance
  fund reserved for bad debt backstop, not profit distribution (C3)
- _createMarket: validate tradeTreasuryFeeBps + tradeMarketMakerFeeBps
  < BPS to prevent bricked markets from 100%+ fees (H3)
- testFeeBpsSumValidation: combined fees >= 100% rejected at creation
- testInsuranceNotDrainedByProfit: profitable close does not draw from
  insurance fund
- testCloseOnlyModeAllowsReduce: add counterparty so vault has
  sufficient balance for PnL payout without insurance fallback
…ange

Regenerate gold_perps_market IDL and TypeScript types across all packages
to reflect the position account PDA derivation added to LiquidatePosition
in commit 49e3590.
@rndrntwrk rndrntwrk marked this pull request as ready for review March 21, 2026 08:29
…emoveAgent

EVM AgentPerpEngine:
- Partial liquidation: cap closedNotionalAtExit at oldNotionalAtEntry
  instead of flooring entryPrice to 0. Prevents equity inflation that
  could block subsequent liquidations after extreme price moves (M-R1).
- _creditMarginFromPool: allow insurance fallback during CLOSE_ONLY
  settlement to prevent deadlock when vault is depleted. Active markets
  still require vault-only profit sourcing (M-R2).
- Add testPartialLiquidationUpdatesEntryPrice regression test (H-R1).

EVM SkillOracle:
- Add removeAgent() with swap-and-pop to prevent unbounded activeAgents
  array growth. Emits AgentRemoved event.

Solana gold_perps_market:
- Add PositionModified, PositionLiquidated, and OracleSynced events
  for off-chain monitoring and indexing.
@rndrntwrk rndrntwrk self-assigned this Mar 21, 2026
@rndrntwrk rndrntwrk requested review from SYMBaiEX and lalalune and removed request for SYMBaiEX March 21, 2026 09:46
@rndrntwrk rndrntwrk requested a review from SYMBaiEX March 21, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant