Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion building-financial-products-in-solana/perpetual-futures.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ The book's standing invariants hold here too. The vault balance always equals `l

- **A stale or manipulated price** is the catastrophic one: with 10x leverage, a 1% price error is a 10% equity error, market-wide, simultaneously. That explains the five checks, the confidence cap, and the one trust decision the program leaves visible: the pool creator chooses the feed, and the feed account's owner is deliberately unchecked. Choose oracles the way you choose custodians.
- **The unliquidated-position gap**: liquidations that pay nothing do not happen, and the LPs absorb the loss. The example documents this gap, and it is why production venues add an insurance fund.
- **First-depositor share inflation**: a donation to the vault moves nothing, because AUM reads `liquidity`, not the vault balance. But `liquidity` grows with every funding payment and trader loss, and a liquidity provider can also be the pool's only trader. An attacker holding a single share can pay funding into the pool from their own position to make that share expensive, then wait for a deposit to round down. The withheld `MINIMUM_LIQUIDITY` makes this cost them, because it counts as 1,000 shares in both divisors: the attacker's share is 1 of 1,001, and almost everything they pay in stays with the locked shares. `test_inflating_liquidity_through_own_trades_does_not_pay` runs the attack: the attacker pays about 1,000 USDC of funding and gets back about 1 USDC. Divided by the bare supply instead, the same attack returns them about 1,500 USDC, 500 of it from the victim's 2,000 USDC deposit. `test_first_deposit_below_minimum_fails` proves a dust first deposit cannot even open the pool.
- **First-depositor share inflation**: a donation to the vault moves nothing, because AUM reads `liquidity`, not the vault balance. But `liquidity` grows with every funding payment and trader loss, and a liquidity provider can also be the pool's only trader. An attacker holding a single share can pay funding into the pool from their own position to make that share expensive, then wait for a deposit to round down. The withheld `MINIMUM_LIQUIDITY` makes this cost them, because it counts as 1,000 shares in both divisors: the attacker's share is 1 of 1,001, and almost everything they pay in stays with the locked shares. A deposit mints deposit × (shares outstanding + 1,000) ÷ AUM. In the test the attacker pumps AUM to just over 1,000 USDC and the victim deposits just under twice that, which mints the victim 2,001 shares against the attacker's 1. Dividing by the bare supply of 1 would have minted the victim a single share. `test_inflating_liquidity_through_own_trades_does_not_pay` runs the attack: the attacker pays about 1,000 USDC of funding and gets back about 1 USDC. Divided by the bare supply instead, the same attack returns them about 1,500 USDC, 500 of it from the victim's 2,000 USDC deposit. `test_first_deposit_below_minimum_fails` proves a dust first deposit cannot even open the pool.
- **Winning too big**: profit beyond the reservation is clipped by the cap rather than bankrupting the pool, and traders know the cap before they open.
- **Sandwiching and price drift**: every money handler takes a user-supplied bound and fails with `SlippageExceeded` instead of filling at a surprise price.
- **Double close, double liquidation**: the position account closes on exit; the second call finds no account.
Expand Down
2 changes: 1 addition & 1 deletion financial-programming-concepts-in-solana/the-adversary.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Bob can protect his own deposit with a minimum-output bound. The vault strategy'
- **Ignore donations.** The program records the pool's size in its own state, as the lending market's `available_liquidity` and the vault strategy's recorded holdings do, rather than reading the vault's token balance. Step 2 then changes the vault's balance and nothing the handler reads. The pool is still one minor unit against one share, and Bob's deposit mints a share per minor unit as it should. The donated 1,000 USDC sits in the vault outside the program's accounting, lost to the attacker, and the lending example tests this with `raw_token_donation_does_not_inflate_exchange_rate`. The vault strategy's `test_donation_does_not_inflate_share_price` runs these exact numbers. A recorded size stops donations only while the attacker has no other way to raise it, and the *Perpetual Futures* chapter shows one: a provider who is also the pool's only trader can pay funding into it.
- **Require a large first deposit.** If the first deposit must be at least 100 USDC, the attacker's smallest position is 100,000,000 shares. Rounding Bob's 1,000 USDC down to nothing would then take a share price above 1,000 USDC, a vault holding a hundred billion dollars.

A program needs one of these four, chosen on purpose. The book's programs each name theirs: the AMM and the perpetual futures pool lock shares, and the lending market and the vault strategy ignore donations.
A program needs at least one of these four, chosen on purpose, and has to check that nothing else it allows raises the pool's value the way a donation does. The book's programs each name theirs. The AMM locks shares, and the lending market and the vault strategy ignore donations. The perpetual futures pool does both: it ignores donations by pricing shares from its recorded `liquidity`, and locks shares because its own traders can raise that figure.

## A True Price Can Still Be a Manipulated Price

Expand Down
Loading