feat(amm): apply trading fees to LP accounting#46
Conversation
| panic!( | ||
| "Add liquidity: AMM Program expects valid Fungible Token Holding Account for Vault A" | ||
| ); | ||
| }; |
There was a problem hiding this comment.
LP calculation should be done on the reserves only (assuming they are up-to-date)
"Not up-to-date" means there's been a donation in the meantime. In which case, one can call syncReserves first.
We might also want to look into syncing reserves on any state changing operation to reduce likelihood of surplus.
There was a problem hiding this comment.
I think this is the main spec mismatch in the PR.
Issue #19 and the parent issue #7 explicitly describe a surplus-based model: the fee stays in the vault above tracked reserves, remove-liquidity pays from actual vault balances, and add-liquidity should use live vault balances after fee accrual so new LPs do not inherit previously earned fees. If we want reserve-only math instead, I think we should rewrite the issue/acceptance criteria first and review that design change explicitly.
Do you want me to do that?
There was a problem hiding this comment.
You can write the issue if you want. @fryorcraken has there been a verdict on whether or not we allow for recover surplus? My understanding was that we don't want/need it, so any surplus can be synced to the pool reserves (which benefits liquidity providers).
LPs are then always calculated based on reserves.
If a surplus has been created before and the LP doesn't call sync before removing, then it would be on them.
46b7ca3 to
34308eb
Compare
3esmit
left a comment
There was a problem hiding this comment.
Revert removal of add_liquidity guard
34308eb to
8cccbc9
Compare
Implement Uniswap V2-style fees-in-reserves: the full swap_amount_in is deposited into the reserve (growing k = reserve_a * reserve_b), while only the fee-adjusted effective_amount_in is used to compute the output amount. This means LPs earn fees proportionally on every removal via k-growth rather than through a separate vault surplus. - swap_logic: add fee_bps parameter; compute effective_amount_in for output formula only; return full swap_amount_in as the reserve deposit - add_liquidity: remove vault balance checks; use pool reserves directly for ratio and LP calculations (vault == reserve invariant holds) - Fix all integration test fixture values to match fees-in-reserves math - Remove dead-code vault_a/b_init_zero helpers from unit tests
8cccbc9 to
04c0490
Compare
| // Compute deposit amount using ceiling division | ||
| // Formula: amount_in = ceil(reserve_in * exact_amount_out / (reserve_out - exact_amount_out)) | ||
| let deposit_amount = reserve_deposit_vault_amount | ||
| // Compute the gross deposit amount the user must pay (fee inclusive). |
There was a problem hiding this comment.
Change the exact-output input formula.
effective_in_min = ceil(reserve_in * amount_out / (reserve_out - amount_out))
deposit_amount = ceil(effective_in_min * FEE_DENOM / (FEE_DENOM - fee))This matches the exact-input rounding and stops the 1-token undercharge case.
|
The issue is that In exact input Lines 172 to 188 in 6d057ae the executed pricing function is: effective_amount_in = floor(input * (10000 - fee) / 10000)
withdraw_amount = floor(reserve_out * effective_amount_in / (reserve_in + effective_amount_in))In exact output Lines 328 to 336 in 6d057ae the code computes: deposit_amount = ceil(reserve_in * amount_out * 10000 / ((reserve_out - amount_out) * (10000 - fee)))That The proof test Lines 2442 to 2483 in 6d057ae shows the concrete case:
So the bug is: exact-output computes |
|
I am bringing back part of the changes from #21, specifically |
|
While fixing the test I realized that the format introduced by @0x-r4bbit, where the rounding happened was more clear and maintainable, so instead of changing the test, I adapted the division to apply that same format where numerator and denominator are separated. I will now request a review from AI and fix any findings they encounter. |
There was a problem hiding this comment.
Pull request overview
This PR updates the AMM to apply Uniswap V2-style “fees-in-reserves” accounting: the pool receives the full (gross) input amount into reserves while the output amount is priced using a fee-adjusted (effective) input, causing LP value to accrue via k growth.
Changes:
- Updated swap math to use fee-adjusted effective input for pricing while depositing the full swap input into reserves; added exact-output fee rounding logic.
- Adjusted add-liquidity logic to rely on pool reserves for ratio/LP computations while still enforcing the vault balance ≥ reserve invariant.
- Updated and expanded unit + integration tests (including new rounding-boundary and fee-accrual scenarios) to match the new accounting.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
amm/src/swap.rs |
Implements fee-adjusted pricing for swaps while keeping gross deposits in reserves; updates exact-output input calculation to account for fee rounding. |
amm/src/add.rs |
Uses pool reserves for add-liquidity math and centralizes vault-balance reads via a helper. |
amm/src/tests.rs |
Updates swap/add-liquidity tests for fees-in-reserves and adds new rounding/fee-enforcement test coverage. |
integration_tests/tests/amm.rs |
Updates fixture values and adds integration coverage for fee accrual across swaps and payout via remove/add liquidity. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
This PR updates the AMM to use Uniswap V2-style “fees-in-reserves” accounting: swaps deposit the full (gross) input into reserves while pricing uses a fee-adjusted effective input, so LPs earn fees via reserve (k) growth and receive them proportionally on liquidity removal.
Changes:
- Update swap math to price with
effective_amount_in(fee-adjusted) while crediting reserves with the fullswap_amount_in; update exact-output swaps to enforce fee rounding correctly. - Refactor vault balance parsing via a shared helper and add new unit tests covering fee rounding boundaries and tiny-swap rejection cases.
- Update integration test fixtures/expectations and add new integration scenarios validating fee accrual across swaps and payout on remove/add liquidity.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
amm/src/swap.rs |
Implements fee-adjusted pricing with gross reserve crediting; updates exact-output math to correctly “lift” effective-in back to required gross-in under floor fee rounding. |
amm/src/add.rs |
Refactors vault balance reads via helper and preserves vault≥reserve invariant checks before LP math. |
amm/src/tests.rs |
Updates expected values for fee-in-reserves behavior and adds rounding/fee enforcement tests for both exact-input and exact-output paths. |
integration_tests/tests/amm.rs |
Updates fixture balances/reserves for new accounting, introduces nonce helpers and reusable execution helpers, and adds integration tests for fee accrual effects. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -80,7 +59,10 @@ pub fn add_liquidity( | |||
| "Vaults' balances must be at least the reserve amounts" | |||
| ); | |||
There was a problem hiding this comment.
Ah, I need to update the commit message. This is about the invariant checks re: token.balance >= vault.reserves that I've originally removed from add_liquidity (which we then decided to put back in)
Implement Uniswap V2-style fees-in-reserves: the full swap_amount_in is deposited into the reserve (growing k = reserve_a * reserve_b), while only the fee-adjusted effective_amount_in is used to compute the output amount. This means LPs earn fees proportionally on every removal via k-growth rather than through a separate vault surplus.