# Incorrect output amount calculation for Trader Joe V1 pools

LBRouter computed V1 pool swap output with wrong constant-product formula, underpaying users on chained swaps through Trader Joe V1 pools.

- Payout: Contest reward
- Severity: high
- Program: Trader Joe
- Credited: Jeiwan
- Disclosed: 2022-12-22
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2022-10-traderjoe-findings/issues/345
- Library page: https://bugbountylibrary.com/r/c4%3A2022-10-traderjoe%3AH-02
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error

**Target:** LBRouter._swapSupportingFeeOnTransferTokens (Solidity, V1 pool branch)
**Root cause:** Output amount used reserve1*(balance-reserve0)*997/(balance*1000) instead of the correct constant-product formula reserveOut*amountInWithFee/(reserveIn*1000+amountInWithFee), omitting the input reserve from the denominator.
**Steps:**
1. Call swapExactTokensForTokensSupportingFeeOnTransferTokens with a path containing a V1 pool (binStep=0).
2. Router enters _swapSupportingFeeOnTransferTokens V1 branch and reads getReserves and token.balanceOf(pair).
3. Observe computed _amountOut = (_reserve1 * (_balance - _reserve0) * 997) / (_balance * 1_000).
4. Compare to JoeLibrary.getAmountOut(_balance - _reserve0, _reserve0, _reserve1) which yields a larger correct amount.
5. PoC: reserves (1e18,1e18), balance 0.0001e18, amountIn 0.0001e18 -> actual 987030000000000000 vs expected 989970211528238869.
**How found:** Code review of LBRouter's V1 swap branch, comparing its inline math against the canonical JoeLibrary.getAmountOut used by LBQuoter and JoeRouter02, spotting the missing reserveIn term in the denominator.
**Exploitation:** Any user swapping across a chain that includes a V1 pool receives less output than the correct AMM formula dictates; the shortfall (e.g. ~2.94e15 wei in the PoC) is effectively lost to the user on every such chained swap.
**Insight:** When a router reimplements AMM math inline instead of calling the shared library, diff the inline formula against the canonical getAmountOut/getAmountIn — a denominator missing reserveIn silently underprices every swap through that branch.
