PerpLandperpland

the ten defenses

Each defense corresponds to a property the protocol promises and a test that proves it. The numbering is chronological — D1–D5 close the single-block flash-loan class that drained Uniperp; D6–D10 close the multi-block follow-up class; M-01 + M-02 close MEV and gas-bomb edge cases.

[D1]single-block flash-pump opens

pre-open price gate

openLong reverts if |spotTick − twapTick| > 5000 ticks at the start of the call. The single most important defense — it removes the attacker's ability to open against a manipulated spot in the same tx that manipulated it.

[D2]hidden price moves via internal swaps

TWAP observes everything

Every swap — including internal hook-initiated leveraged buys, closes, and liquidations — writes an observation to the TWAP ring buffer. Same-block pumps poison their own TWAP, so the spot/TWAP gap closes quickly.

[D3]

(retired)

The original per-block borrow-only cap was strictly dominated by D7 (curve-advance cap). Retired in v0.1.

[D4]open + self-liquidate in same tx

symmetric liquidation cooldown

Liquidation skips positions younger than LIQ_COOLDOWN_BLOCKS = 2 — the same gate as user-close. No asymmetric exit path.

[D5]donation-based pool manipulation

donate blocked

beforeDonate is registered and reverts. Anyone trying to donate tokens to the pool (which would skew the active band's fee accumulator) gets blocked.

[D6]single-open price impact attacks

post-open price gate

After the open's leveraged buy executes, spot must STILL be within deviation of TWAP. Chains opens against each other within a block — second open in a block has less budget than the first.

[D7]multi-block pre-buy + pump attacks

per-block curve-advance cap

MAX_CURVE_ADVANCE_PER_BLOCK = 5 ETH. Total curve-advancing ETH per block (direct buys + opens combined) is capped. Forces both legs of a multi-block attack to spread across many blocks, each requiring TWAP catch-up.

[D8]patient accumulation attacks

absolute borrow ceiling

Total outstanding band borrow can't exceed MAX_TOTAL_BORROW = 200 ETH at any time. Even if D7 is somehow beaten, this caps absolute capital at risk to abandoned positions.

[D9]ongoing exploitation after first hit

bad-debt auto-pause

When totalBadDebtETH > 5 ETH, new opens auto-revert. Closes and liquidations continue so users can exit. Owner heals via donateAndRefillBands.

[D10]indefinite-hold abandonment

funding rate on positions

50% APR on debt, virtual accrual. Even with no price movement, an abandoned lev-5 position becomes liquidatable in ~1 year. Closes the 'open and never close' exit path that made the original Uniperp drain profitable.

[M-01]MEV bots front-running liquidations

liquidation sandwich defer

If current spot has drifted from TWAP by more than 1500 ticks at scan time, the scan exits early. Once arbitrage closes the gap, the next external swap re-triggers fairly. Bots can't sandwich a forced sell.

[M-02]gas-bomb staking bricking liquidations

gas-cap on staking call

staking.notifyReward is called with gas: 100_000. A malicious staking that consumes all gas can no longer starve the catch handler via the 1/64 rule.