Skip to content

test(amm): LP reward regression coverage for the 2026-07-21 findings - #4

Open
OxToF wants to merge 3 commits into
mainfrom
test/lp-reward-coverage
Open

test(amm): LP reward regression coverage for the 2026-07-21 findings#4
OxToF wants to merge 3 commits into
mainfrom
test/lp-reward-coverage

Conversation

@OxToF

@OxToF OxToF commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Follow-up to #3 — adds the test coverage that was entirely missing on the AMM LP-reward paths.

Isolated rewards-enabled pool (two throwaway mints, no interference with the existing SOLA/USDC / curve / POL / invariant tests). Covers, in-epoch:

  • Happy path — a real depositor claims continuous oSOLA (previously uncovered).
  • Finding B guard — a fresh wallet holding transferred LP calls claim_lp_rewards → must revert NothingToClaim. This encodes the exact confirmed devnet exploit as a permanent regression test.
  • Finding A guard — a fresh wallet calls checkpoint_lp → banks zero weight, so the same LP walked through N wallets can no longer inflate the epoch pot.

Documented gap

The full epoch-emission cycle (emit_pool_rewardsclaim_lp_emissions, incl. the osola_claimed cap) requires crossing a 7-day epoch boundary, which this mocha/validator harness can't warp. That path stays uncovered here and needs a bankrun-style clock — flagged before mainnet emission arming.

Verification

anchor test --provider.cluster localnet37 passing, 0 failing. Must run on localnet: the tests call configure_continuous_emissions, which writes global state and would clobber the live devnet config.

🤖 Generated with Claude Code

Isolated rewards-enabled pool (two throwaway mints). Covers, in-epoch:
- happy path: a real depositor claims continuous oSOLA (was uncovered)
- Finding B guard: a fresh wallet holding TRANSFERRED LP reverts NothingToClaim
- Finding A guard: a fresh wallet banks zero checkpoint weight

Full epoch-emission path (emit_pool_rewards -> claim_lp_emissions) needs a 7-day
epoch warp this mocha/validator harness can't do — documented gap (bankrun).
37 passing on localnet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
soladrome-lzn5 Ready Ready Preview, Comment Jul 22, 2026 4:59pm

OxToF and others added 2 commits July 22, 2026 18:56
The mocha suite runs against a live cluster (Anchor.toml pins devnet), so it is
stuck with the real clock and could only cover the in-epoch half of the LP reward
system. The per-epoch half (checkpoint_lp -> emit_pool_rewards ->
claim_lp_emissions) needs a 7-day boundary crossed, which is the documented gate
on arming configure_emissions with initial > 0 on mainnet.

bankrun's setClock() moves the epoch on demand. 7 tests, ~1s, no validator:

- full cycle across a real epoch boundary: allocation is the undecayed emission in
  epoch 0, then decayed 1% in epoch 1, which pins decayed_emission too
- replay of the same (user, pool, epoch) rejected. Worth noting the LpEpochClaim
  `init` is an ACCOUNT CONSTRAINT, so it fires during validation, before the body's
  NothingToClaim. The opposite assumption was made and disproved by the test.
- invariant sum(claims) <= osola_allocated over a 2-LP epoch. The clamp itself does
  not bind: two honest LPs under-subscribe the pot, which is the safe direction.
- a late depositor cannot bank a full epoch of weight (Finding A)
- withdrawal decrements lp_amount, so it shrinks the reward basis
- reward_basis follows the wallet balance DOWN, the reverse leg of Finding B that
  nothing covered: high recorded deposit but LP moved out must bank zero weight
- a position recorded at lp_amount = 0 (legacy or transfer-acquired) can still
  withdraw

Verified load-bearing by mutation: putting window_start back to epoch_start in
checkpoint_lp makes the late-depositor test fail (weight/LP 1114620 vs 1114560,
equality being the back-credit signature) while the other 6 stay green. A cycle
test asserting only "gained > 0" passes on the bug.

Tooling trap: bankrun cannot load an SBPFv3 binary. cargo build-sbf --arch v3,
required for a devnet deploy, yields a .so rejected with the misleading "Program is
not deployed". Build plain to test, re-add --arch v3 to deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sync found

Two entries. The missing per-epoch coverage is written (bankrun), and a delivery
defect was found on the way: the committed IDL carried lp_user_info for
add_liquidity, claim_lp_rewards and remove_liquidity but NOT for checkpoint_lp,
precisely the subject of 8e4454d. The production bundle was inspected and serves
checkpoint_lp with 9 accounts while the deployed program expects 10.

Nothing caught it because LpEmissions.tsx passes lpUserInfo through an `as any`
cast, and the component is not mounted in page.tsx, so no tester could reach the
path. Same failure mode as the "always rebuild the IDL" rule, one notch more
devious: the IDL had been regenerated and partially committed. Check the IDL that
is SERVED, not the local one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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