feat(token-swap): add pinocchio example - #716
Conversation
Greptile SummaryThe PR adds a Pinocchio implementation of the token-swap example, including AMM and pool creation, liquidity management, swaps, tests, and workspace integration. The latest account-binding changes rederive the pool authority, canonical reserve accounts, and liquidity mint before those accounts participate in settlement.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the current canonical reserve and liquidity-mint checks address all three previously reported account-substitution paths before pricing, share accounting, or settlement. Important Files Changed
Reviews (2): Last reviewed commit: "token-swap: bind the pool vaults and liq..." | Re-trigger Greptile |
PoolSeeds::load rederived the pool and its authority but never checked
the two vaults, and neither deposit nor withdraw checked the liquidity
mint. Anchor gets those from associated_token::mint/authority and the
mint's seeds constraint; dropping them in the port left three ways to
drain a pool:
- swap with a zero-balance stand-in for the paying reserve prices the
trade against nothing and empties the opposite vault;
- deposit into caller-controlled reserves still mints genuine LP
shares, redeemable against the real ones;
- withdraw against a caller-created LP mint sets the entitlement to
whatever they like.
Rederive both vaults as the authority's associated token accounts, and
the liquidity mint from its seeds. Each vector has a test verified to
succeed without the checks.
|
@amilz could you take a look at this one when you get a chance? No open review threads left on it, so it is ready for maintainer review. It is one of 23 open Pinocchio ports I have up — they are independent and self-contained, so they can be reviewed and merged in any order: https://github.com/solana-developers/program-examples/pulls/MarkFeder |
Adds a Pinocchio implementation of
token-swap, alongside the existing Anchor one.What it does
A constant-product AMM. Five instructions:
CreateAmm,CreatePool,DepositLiquidity,WithdrawLiquidityandSwapExactTokensForTokens.Everything a pool owns hangs off a single authority PDA — both vaults and the LP mint — so the program can move pool funds without any wallet holding that power.
The parts worth reading
MINIMUM_LIQUIDITYis burned on the first deposit and never minted to anyone. It keeps the pool from being emptied completely, which is what would otherwise let the share price be skewed while the pool is near-empty. Withdrawals divide bysupply + MINIMUM_LIQUIDITYfor the same reason.a * bis read again from the vaults after the transfers; a higher value is fine (rounding in the pool's favour), a lower one aborts.amountfails at the burn and rolls the two transfers back, so the pool cannot be drained by asking for more than you hold.All the arithmetic goes through a
mul_divhelper that widens tou128, so the product of twou64balances cannot overflow.Account binding
The pool records its AMM and both mints.
PoolSeeds::loadreads them back, checks the supplied mints against the stored ones, and rederives both the pool and the authority — so a caller cannot pair a real pool with unrelated token accounts, or point a pool at a cheaper AMM's fee. There is a test for the mint substitution.Differences from the Anchor version
swap_ais au8.CreateAccount, so a stray lamport on a derivable address cannot block pool creation (see feat(merkle-tree-token-claimer): add pinocchio example #714).Tests
10 LiteSVM tests: the full lifecycle from AMM through pool, deposit, swap, ratio-trimmed deposit and withdrawal, plus fee, slippage and mint-substitution rejections. The swap test recomputes the expected output from the curve in the test rather than asserting a hardcoded number. Verified locally:
tsc --noEmit,pnpm test,prettier --check,cargo fmt --check,cargo clippy -D warnings.