Odysseus Perps: SPEC
Long and short markets on Elysium (chain 99901) pad coins that have no Hyperliquid market, settled in WHYPE against a vault. Source plan: the “Odysseus Perps” advertising plan (artifact 4tFRsNaNopJ2AJKqMKqQ6K, 2026-11-07). Phases 1 SPEC and 2 CODE were written together in one session at Alexandru’s request; both wait for his OK.
1. Contracts
| Contract | Role |
|---|---|
SlowOracle |
Reads the coin’s own Uniswap v4 pool (getSlot0 via extsload, any PoolManager). Stores a mark that moves toward spot at most stepPpmPerSecond per second (20 to 1,000 ppm, set at listing), counting at most 300 s per update. Only the keeper’s readings move it (pokeMark on Perps, the pool within 1% of the spot the keeper saw); anyone’s reading (poke, every trading quote) only records the recent extremes of a healthy pool (gate run 11). Price unit: HYPE per coin, 1e18. 18-decimal tokens only. |
OdysseusVault |
ERC4626 over WHYPE (oHYPE). Counterparty to every position. 24 h lock after the holder’s own deposit; locked shares cannot be transferred; deposits only for yourself. |
OdysseusPerps |
Markets, positions, fees, funding, liquidations, caps. |
2. Money flows
| Flow | From | To |
|---|---|---|
| Open | trader margin (WHYPE) | Perps; 0.30% of size taken as fee |
| Fee split (open and close) | Perps | 50% vault (sent now), 20% creator, 10% pad (treasury if no pad), 20% treasury (accrued, pulled with claim) |
| Close at a loss | Perps | loss to vault, close fee split, the rest to the trader |
| Close at a profit | Vault payOut (capped at the vault balance, first come first served: not reserved against LP withdrawals) |
Perps, then fee split, then trader |
| Liquidation | Perps | loss to vault, 1% of size to vault, the rest (if any) to the trader; no liquidator reward |
| Funding | heavier side pays fundingPerHour x skew per unit; the lighter side receives the same rate per unit; the vault keeps the difference |
settled inside equity at close, settle or liquidation |
| Borrow fee | every position, long and short alike, 0.005% of its size an hour (BORROW_PER_HOUR; decided by Alexandru, 2026-11-08) |
stays with the vault, settled inside equity like funding; it is collected only out of equity, so a position whose equity is capped at 5x margin, or already below zero, pays it in effect only down to that line (the cap and the vault’s take of a loss are unchanged) |
| Settle (a market delisted for 7 days, healthy pool) | Perps, called by anyone | at the price and fee the trader’s own close would get (loss to vault, close fee split, the rest to the trader), but with no price limit and at a moment the caller picks; it reverts while the pool reads thin, or while the spot or the trader’s side of the recent extremes is more than 2% from the mark as stored before the call (gate run 10: settle’s own poke would move it), so a push can price it by at most that 2% band |
| Settle dead (a market delisted for 7 days, thin pool) | Perps, called by the lister only (settleDead; decided by Alexandru after gate run 9) |
the same payout as the trader’s own close at that moment: the worst-of price (mark, spot, recent extremes), the close fee, the rest to the trader; works while paused; refused (Slippage) beyond the limit price the lister checked off chain (gate run 10) |
| LP | WHYPE in / out of the vault | share price rises with losses and fees, falls with profits |
Prices: entry and exit at the worst for the trader of the mark, the spot and the pool’s recent extremes (lowest and
highest healthy spot of the last 10 to 20 minutes): a long enters at the highest and exits at the lowest, a short the
opposite (decided by Alexandru, 2026-11-08). New readings only lower the low and raise the high, so a swap and a swap
back inside one transaction cannot improve a trader’s price; a griefer can make it worse for up to 20 minutes, which
the price limit refuses. Every open and close carries limitPrice and deadline; an open that would be
liquidatable at the mark reverts. Liquidation at the mark only. If the pool has no price (sqrtPrice == 0), close
and liquidate use the last stored mark and extremes; any other oracle failure reverts (so low gas cannot force them).
A pool reads “thin” when the liquidity of its locked or burned position, named at listing (owner, range, salt), falls
under a floor set in the position’s own units (the liquidity at which it would hold 25% of the base depth in HYPE at
the pool’s price when the lister lists or re-vets the market; the position must hold at least that much real HYPE then,
and the pool must be within 2% of the price the lister expects, so a pushed pool cannot set it), or when its price leaves that
position’s range. A price move alone never makes a pool thin, and recovering from one never needs the lister. Nobody
can add to or take back a locked position inside a transaction, so thinness cannot be bought with a swap and a swap
back or with liquidity of one’s own. A thin pool’s reading moves neither the mark nor its clock, but it does go into
the recent extremes (since gate run 9), so a dead pool’s price reaches every exit; a thin reading can only make an
exit worse for the trader. A push out of the range inside one transaction can therefore record a false extreme: the
same griefing, at the same cost, as a push on a healthy pool (section 7).
A price pushed out of the range inside one transaction does read thin to that transaction, so nothing permissionless
treats a thin reading as a dead pool: a delisted market whose pool reads thin is settled by the lister (settleDead),
and anyone else’s settle is refused there (gate run 9; the one-hour thin clock of run 8 is gone).
3. User flows
- LP: approve WHYPE,
vault.deposit(assets, self), wait 24 h,redeem/withdraw. - Trader: approve WHYPE to Perps,
open(token, isLong, margin, size, limitPrice, deadline)withmargin <= size <= margin x maxLeverage(1x up to the market’s maximum);close(token, isLong, limitPrice, deadline). - Anyone:
liquidate(token, trader, isLong)when equity at the mark < 2% of size;settle(token, trader, isLong)on a market closed to new positions for 7 days (it pays what the trader’s close would at that moment; unlike a close it has no price limit and the caller picks the moment, so it reverts while the spot or the trader’s side of the recent extremes is more than 2% from the mark (only the keeper moves it, so a caller cannot prime the band; gate run 11): a pushed price or a recorded false extreme can price it by at most that 2% band, and beyond it only delays it; it also reverts while the pool reads thin, a pushed-out range included: dead pools are the lister’s, flow 5);oracle.poke(token)andoracle.quote(token)(the latter also returns the stored mark and the recent extremes; it is what the perps contract calls): both record a healthy pool’s reading into the recent extremes and never move the mark; on a thin pool they record nothing (gate run 11, H1 and L1). - Creator, pad, treasury:
claim(). - Lister:
listMarketafter the coin clears the public bar (off chain), naming the full v4 pool key, the pool’s locked position (owner, range, salt) and the price it expects (expectedPrice; the pool must be within 2% of it at listing),setActive,setMaxLeverage,setFundingPerHour,setBaseDepth(token, base, expectedPrice)(once an hour, within half to double the last base and never underMIN_BASE_DEPTH, the pool within 2% ofexpectedPrice),setLockedPosition(token, owner, lower, upper, salt, base, expectedPrice)for a lock that migrated in the same pool (once an hour, sharing the timer; the listing checks; the base can only stay or go down here, and the depth resets to it). That cut is not rate-limited: one call can take the base down to any value fromMIN_BASE_DEPTH(1 HYPE) up, since a lock that migrates to a much smaller position needs it. Only raising the base is a step an hour. No base change can leave a reserve floor of zero (_setFloorreverts).setActive(token, false)stops opens and starts the 7-day clock after which anyone can settle the market’s positions on a healthy pool;setActive(token, true)stops it. Once the 7 days have passed and the pool reads thin (its lock pulled, or the price out of the lock’s range),settleDead(token, trader, isLong, limitPrice)settles a position at what its owner’s close would get (worst-of price, close fee), also while paused; only the lister can call it. It refuses an exit worse thanlimitPricefor the trader (a long’s lowest, a short’s highest): the lister checks the dead pool’s price off chain and signs that number, because on a pulled pool anyone can move the price and its recent extremes for gas (gate run 10, M2). - Keeper:
setDepthhourly (within half to double, and within a quarter to four times the base);pokeMark(token, expectedSpot)every listed market at least once a minute: the only call that moves the mark (one step toward the pool’s spot, at most step x 300 s) and the only reading of a thin pool that is recorded; it reverts when the pool sits more than 1% (KEEPER_BAND_BPS) from the spot the bot saw off chain, so a sandwich around it only delays it. Fresh marks, recorded dead prices and liquidations depend on it. Liquidate. - Pauser:
pause/unpauseopens,revokeKeeperandrevokeListerat once.
4. Roles and keys
| Role | Holder (mainnet plan) | Can | Cannot |
|---|---|---|---|
| DEFAULT_ADMIN (all three) | TimelockController, 2 day delay (enforced on 99901) | grant roles (including the vault’s PERPS_ROLE, so in effect anything, after the delay), set treasury, allowlist PoolManagers | act before the delay; LPs have time to leave |
| LISTER (Perps) | ops multisig | list pools on allowlisted PoolManagers (the key is checked to pair the coin with HYPE), name the creator, delist (stops opens; 7 days later anyone can settle the market’s positions at the price a close would get while the pool is healthy and in band), settle a delisted market’s positions on a thin pool (settleDead, 7 days after delisting, at the price the owner’s close would get), leverage 1-5x, funding up to 1%/h, re-vet base depth and the reserve floor (the pool within 2% of an expected price), re-point a migrated lock (setLockedPosition, the base only down but in one call, to 1 HYPE at least; the floor never zero) |
touch positions directly or settle them sooner than a week after delisting; a rogue lister could still list a bad but valid pool, which is why it is a multisig |
| LISTER (Oracle) | the Perps contract only | add feeds; the keeper’s mark update (keeperPoke), only through pokeMark |
change a feed |
| KEEPER (Perps) | keeper bot key (a second key as backup) | move the mark one step toward the pool’s spot (pokeMark, the pool within 1% of the spot it names); depth within 0.5x-2x of the last value, once an hour, never outside 0.25x-4x of the lister’s base |
the reserve floor, anything else |
| PAUSER (Perps) | ops multisig | pause / unpause opens, revoke the keeper at once | block closes or liquidations |
| PERPS (Vault) | the Perps contract only | payOut |
anything else |
5. Invariants (in words)
Each is a handler-driven invariant in test/invariant/Protocol.t.sol (traders, LPs, admin, keeper and the pool all
acting at random), plus unit tests. State rules are invariant_*; rules about one action are checked right after the
action and counted (“ghosts” that must stay 0).
- Perps holds exactly the open margin plus unclaimed fees (
balance == totalMargin + totalAccrued).invariant_perpsHoldsMarginPlusFees(alsoBooks.t.sol). - Each side’s open interest equals the sum of open sizes; an open never leaves a side above min(25% of depth, 10% of
the vault).
invariant_openInterestIsTheSumOfPositions, ghostsideCapBreaches. (Depth can later shrink below open interest through the keeper; existing positions stay, new ones on that side are refused.) - An open that increases |long OI - short OI| leaves it at most min(5% depth, 10% vault). Ghost
netCapBreaches. Across markets, the larger sides added up stay within 25% of the vault (GLOBAL_CAP_BPS, decided by Alexandru on 2026-11-08): a winner takes at most 4x its size, so even every coin going 5x at once is paid in full.invariant_globalExposureIsTheLargerSide,test_globalCapAcrossMarkets. Closes cannot be refused, so after a close the net can be larger; it is still at most one side, which rule 2 kept within 10% of the vault when it opened. Every position is at least 1x (size >= margin): the caps count size and the payout cap counts margin, so a size below the margin would let a winner take more than 4x what the caps counted. GhostsubOneOpens. The global bound is checked when an open raises the larger sides: ghostglobalCapBreaches(each such open leaves them within 25% of the vault as it was). LP withdrawals afterwards can leave them above 25% of a smaller vault; opens that would raise them are then refused until it is back under. - Closing an open position never reverts (with no price limit), paused or not, market active or not, pool thin or
not, at any price the pool can report. Ghost
closeFailures;test_shortClosesAtAnAbsurdPrice. - A close pays at most the equity at the mark and at most 5x margin. Ghost
overpaid. - A pool push inside one block cannot liquidate a position that was healthy at the mark as last updated. Ghost
wickLiquidations; no position is liquidated above maintenance, ghostunearnedLiquidations. Only the keeper moves the mark (gate run 11, R11-H1): before, anyone could push, poke and push back in every block and walk the mark at the full step for gas (an 18% walk in 33 minutes liquidated a healthy 5x long). Anyone’s poke now only records the reading (test_R11H1_aWalkOfAnyonesPokesNeverMovesTheMark; ghostattackerMarkMoves, actionwalkMark). A push around the keeper’s own call can bias its update by at most the 1% band; beyond it the poke reverts and the mark waits (ghostoffBandKeeperPokes, actionsandwichKeeper). One keeper update moves the mark by at most step x 300 s: 3% at the default 100 ppm a second, 30% at the 1,000 ppm ceiling (the whitepaper says so too), toward a spot within 1% of what the keeper saw. - Equity never exceeds 5x margin.
invariant_equityNeverAboveFiveTimesMargin. - While the locked position’s liquidity is under its floor, or the price is outside its range, the mark does not move
and nothing opens; a dip and recovery does not freeze (
test_aDipAndRecoveryDoesNotFreeze). GhoststhinMarkMoves,thinOpens. - No vault withdrawal inside the 24 h lock. Ghost
lockedWithdrawals. - A swap and a swap back inside one transaction cannot improve an entry or an exit once a move has been recorded
(unit tests
test_roundTripCannotUndoARecordedCrashand...Pump); a sandwiched open is refused by its limit or as liquidatable at birth (test_sandwichedOpenIsRefused). A move that leaves the pool thin (a pulled lock, a crash past its range) is recorded too, by the keeper’s pokes, so a long cannot push the price back up inside its own close and leave at the frozen mark (test_r9_pulledPoolLongCannotExitAtFrozenMark,test_r9_crashPastRangeLongCannotExitAtPreCrashPrice; ghostdeadPoolOverpays, actiondeadPoolRoundTrip). - Liquidity of one’s own, added and taken back inside one transaction, cannot unfreeze a pulled pool: thinness reads
only the locked position (
test_flashLiquidityCannotUnfreezeAPulledPool; handler actionflashLiquidity). - A position
equityOfcalls liquidatable can always be liquidated. GhostblockedLiquidations. - A position on a market closed to new positions for 7 days can be settled by anyone while the pool reads healthy,
paying no more than its equity and at most 5x margin; on an open market, or sooner, it cannot. It goes through
exactly when the spot and the trader’s side of the recent extremes are within 2% of the mark as stored before the
call, so a griefer who records a false extreme beyond the band only delays it, and one inside it prices it by at
most 2%. Ghosts
blockedSettles,unearnedSettles,offBandSettles,griefedSettles,reachSettles(a push sized to the poke’s own reach plus the band, gate run 10),overpaid; handler actionsgriefThenSettle,pokeReachSettle; unit teststest_settleADelistedMarket,test_settleRefusesAGriefedLow,...High,...APushInTheSameBlock,test_R10M1_bandFromThePrePokeMark_step100,..._step1000. On a pool that reads thin, a price pushed past the lock’s range edge included, anyone’s settle is refused (gate runs 8 and 9). GhoststhinSettles,rangeExitSettles(handler actionrangeExitThenSettle, primed or not, on a lock bounded on the coin-down side, as BLOB’s is); unit teststest_rangeExitInTheSettlingTransactionIsRefused,test_noThinClockToPrime. The lister settles a delisted market’s positions on a thin pool (settleDead), exactly 7 days or more after delisting and only while the pool reads thin, paying no more than the owner’s own close at that moment, and never at an exit worse than the lister’s checked limit. GhostsunearnedDeadSettles,blockedDeadSettles,deadSettleOverpays(handler actionsettleDead); unit teststest_settleAFrozenMarketReleasesTheCap,test_listerSettlesACrashedMarket,test_settleDeadRefusals,test_R10M2_settleDeadHonoursTheListersLimit. - Every position pays the borrow fee, so a matched long and short cost their holder something every hour even with
no skew (
test_borrowFeeChargesAMatchedPair); over any stretch of time the two side indexes together grow by twice the fee (ghostborrowShortfalls). - No base change ignores
expectedPriceor leaves a reserve floor of zero; no base, at listing, re-vetting (setBaseDepth) or a lock migration, is underMIN_BASE_DEPTH(1 HYPE), so a migrated lock can always be followed; andsetLockedPositionnever raises the base. GhostsbandBypasses,zeroFloors,badBaseCuts(handler actionssetBaseDepthwith a fuzzed expected price,setLockedPosition); unit teststest_setLockedPositionKeepsAFloor,test_setBaseDepthNeverZeroesTheFloor,test_baseDepthMinimumEverywhere.
Fork (test/fork/ElysiumFork.t.sol, live Elysium 99901): the oracle reads BLOB’s real pool on ElysPump’s v4
PoolManager (a fork of v4) and an empty-range pool on the official one through the canonical storage layout; open and
close on BLOB; a pushed slot0 moves the mark 3% per update; an empty-range pool cannot be listed; a pulled BLOB pool
freezes and a close limit refuses its pushed price; a pushed BLOB pool with its liquidity stays healthy; the mainnet
WHYPE is a plain fully backed wrapper.
5b. Chain facts (checked 2026-11-08)
- Mainnet 99901 WHYPE:
0xbA876237B0F021A6c6D852d6c26E107997cC5E81. Every contract of the official Uniswap suite on 99901 (v2 router, v3 and v4 periphery, deployed by0x2179...27B6) points at it; it has the most holders (3,248); it is fully backed (10,600.81 WHYPE against 10,600.81 HYPE); deposit and withdraw work on a fork. Its withdraw reverts when the receiver is a contract that refuses HYPE; Odysseus never unwraps. - Testnet 99801: RPC
https://rpc-elysium-testnet.t.conduit.xyz; no Uniswap v4 and no WHYPE we could verify, soscript/testnet/Setup.s.soldeploys a WETH9-style WHYPE, our own PoolManager and a stand-in coin and pool. - Deployment:
script/Deploy.s.sol, the same on 99801 and 99901, deploys a TimelockController (1 day minimum delay off anvil, a canceller that is neither the proposer nor the deployer) and hands it every admin role.
6. Off chain, on purpose
The listing bar (holders >= 300, age >= 72 h, top 10 < 35%, dev < 10%, liquidity locked or burned, no owner / mint /
pause / blacklist, tax <= 3%, market cap >= $300k, daily volume >= $100k, depth >= $60k, depth / mcap >= 3%) is
checked by a public script and enforced by the LISTER role. Leverage tiers ($60k-150k: 2x, $150k-500k: 3x, $500k+: 5x)
are converted to maxLeverage at listing. Depth (HYPE) is posted by the keeper.
7. Known gaps
-
Decided (gate run 3, M1): a pool pulled for good has no price. A close then settles at the worst for the trader of mark, spot and the recent extremes, so a griefer can push such a pool’s spot down for free and a long cannot close above its limit (and is not liquidated, since the mark is frozen). Settling at the frozen mark instead would let longs on a rugged coin take the pre-rug price out of the vault, and it was that rule which opened the run 2 HIGH. So the vault-protective rule stays; a position on a dead pool closes at what the pool says, or waits. Since gate run 6 it cannot wait for good: the lister delists the market and 7 days later settles it at that price (since gate run 9 only the lister settles a thin pool:
settleDead). The rule needs the dead price among the recent extremes: until gate run 9 a thin poke recorded nothing, so a long could swap the spot back up inside its own close and leave at the frozen pre-crash mark (R9-M1). The keeper’s poke of a thin pool now records the spot (the mark and its clock still stay frozen); since gate run 11 nobody else’s reading of a thin pool is recorded (a pulled pool’s price moves for gas, and false extremes re-recorded every ten minutes would delaysettleDead, R11-L1). The extremes lapse 10 to 20 minutes after the last keeper poke, so the keeper’s once-a-minute pokes keep the dead price in force. -
Griefing the recent extremes: recording a false 40% low costs about 0.16% of the pool’s HYPE reserve per 10-minute bucket (a swap and a swap back) and blocks long exits above it for 10 to 20 minutes; the price limit makes them revert instead of paying the false price.
-
Pushing the price out of the locked position’s range inside one transaction makes that transaction’s reading thin: the mark holds still, which only helps the vault, and it cannot relax any permissionless rule: a thin reading refuses
settleoutright (decided by Alexandru after gate run 9; the run 8 thin clock, and with it R9-L1 and R9-L2, is gone). Since gate run 11 a thin reading records nothing unless the keeper makes it. -
Trust added to the lister (gate run 9 decision):
settleDeadtakes the pool’s thinness at the call, and a thin reading can be made inside one transaction. A rogue lister key could make any delisted market read thin and settle its positions at a pushed worst-of price: on a range-bounded lock by pushing the price out of range; on any lock, full range included, by re-pointing the market withsetLockedPositionto a small position it holds and pulling that (test_R10_rogueListerRepointIsTheWrittenResidual). Bounds: only a market delisted for 7 days (traders had the week’s notice and their own close all along), the payout is what the owner’s close would get at that moment, the margin goes to the vault (not to the lister), the lister is a multisig and the pauser revokes it at once (revokeLister). Third parties cannot price an honest lister’s call (gate run 10, M2):settleDeadcarries alimitPrice, and since gate run 11 only the keeper’s readings of a thin pool are recorded. Operating rules: the lister callssettleDeadonly on a pool the keeper’s pokes have seen dead, with a limit checked against the pool off chain; signers check that number. -
The listing bar must name the pool’s locked or burned position; a pool whose liquidity is in many small positions, or not locked, cannot be listed.
-
A lock that migrates to a new position in the same pool is followed with
setLockedPosition(lister, once an hour, same checks as listing; a smaller new lock is named with a smaller base). A coin whose liquidity moves to a new pool cannot be listed again (AlreadyListed); its market is delisted and settled after 7 days. -
The listing bar must check that the locked position’s owner cannot move it (BLOB’s belongs to the ElysPump hook, whose code can reach the PoolManager): a lock its owner can pull in one transaction defeats the floor.
-
Decided by Alexandru on 2026-11-08 (gate run 6, M2): a matched long and short count toward the global cap at their larger side, since a hedged pair at different leverage still carries payout risk for the vault. They pay the borrow fee every hour (a block of 250 HYPE of exposure costs about 18 HYPE a month at 1x), and the lister can delist a market used to block the cap; a week later anyone settles it (the lister, if its pool is thin). A blocker with deep pockets can still hold the cap for about a week per market, at that cost. Traders on a delisted market get the same week’s notice (
MarketActive). -
Settle’s band (gate runs 7 and 8): a griefer who keeps recording false extremes can delay settling a delisted healthy market for as long as they pay for it. Blocking needs only a false extreme just over 2% per 10-minute bucket: about 0.006% of the pool’s HYPE reserve per bucket at a 0.3% fee (a swap and a swap back), twice that to block both sides, and little more than gas on a zero-fee pool. Inside the band a push can still price a settle, by at most 2% of the price; beyond it, it only delays it. The band is measured from the keeper’s mark: gate run 10 measured it from the mark before settle’s own poke, and gate run 11 showed anyone could still move that mark with a poke of their own first (R11-M1); only the keeper moves it now. A mark slower than the pool’s trend also keeps the band refusing while the trend lasts; the step has a floor of 20 ppm a second (7.2% an hour), so only a coin trending faster than that for hours delays its settles. A pulled or crashed pool is the lister’s to settle (
settleDead), at the worst-of rule, so its positions settle at what the pool says (gate run 3). -
The lister’s base powers (gate run 7, L1):
setLockedPositioncan cut a market’s base, its caps and its reserve floor in one call, down to 1 HYPE of base. Caps only get safer for the vault; the floor stays above zero, but a lister key and the lock’s owner together could cut the floor first and then pull most of the lock without the pool reading thin. Mitigations: the lister is a multisig, the pauser revokes it at once (revokeLister), and the listing bar requires a lock its owner cannot pull. -
Accepted by Alexandru on 2026-11-08 (review finding 1, HIGH): winner payouts are not reserved against LP withdrawals and are paid first come first served. Disclosed here, in section 2 and in the whitepaper.
-
Unrealized trader PnL is not in
totalAssets; an LP who knows a big payout is coming can exit after the 24 h lock. -
No liquidator reward: liquidations depend on our keeper (anyone can still call).
-
Fixed 2026-11-07: equity is capped at 5x margin (
MAX_EQUITY_MULTIPLE), so one winner takes at most 4x its margin. Payouts are still capped by what the vault holds at close; if it is empty, winners are paid partially. -
Not yet built: the listing-bar script and the keeper bot (deep invariants and fork tests now run, see FAZE.md).
-
Still open in price terms: a sharp move that nobody has recorded yet (no poke since it happened) can be traded at the stale mark by whoever acts first; the keeper must poke every few seconds to keep that window short.
-
Keeper liveness (decided by Alexandru on 2026-11-08, gate run 11, H1): only the keeper moves the mark. If the bot stops, the mark freezes: traders still close at the worst of mark, spot and recent extremes under their own price limit, opens price the same way and refuse a position liquidatable at birth, and liquidations wait at the frozen mark (a loser’s loss is capped at its margin, so a late liquidation delays the vault’s gain rather than costing a payout, though the trader keeps a free option meanwhile). Mitigations: a second keeper key as a backup, the pauser pauses opens. A leaked keeper key moves the mark one step per poke toward a spot it can push itself (the run 11 walk needs that key now); the pauser revokes it at once (
revokeKeeper). -
Pulled pools: the oracle reads the locked position’s liquidity; under its floor the mark freezes and opens revert. Liquidity removed down to just above the floor is still possible; the lock check stays in the bar.
-
The pool’s hook is not checked on chain (a hook can change fees or swap accounting): the listing bar must vet it.
-
setPoolManager(manager, false)stops new listings on that manager, not markets already listed on it. -
The admin (the timelock) can grant any role, including the vault’s PERPS_ROLE, which pays out: the timelock delay (2 days on mainnet, longer than the 24 h share lock) is the protection; watch
CallScheduled. -
18 decimals assumed for every coin; a coin with other decimals must not be listed.