# User wrapped tokens get stuck in master router because of incorrect calculation

JalaMasterRouter.swapExactTokensForETH passes unwrapped amountIn to JalaRouter02, leaving wrapped tokens stuck in the router where anyone can steal them via swapExactTokensForTokens.

- Payout: Contest reward
- Severity: medium
- Program: Jala Swap
- Credited: Arabadzhiev
- Disclosed: 2024-02-21
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2024-02-jala-swap-judging/issues/146
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-02-jala-swap%3AM-2
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:source-review vuln:business-logic vuln:precision-loss

**Target:** JalaMasterRouter.swapExactTokensForETH
**Root cause:** swapExactTokensForETH forwards the pre-wrap amountIn to JalaRouter02 instead of multiplying by the token's decimal offset, so only part of the wrapped tokens are swapped and the remainder stays in the master router.
**Steps:**
1. Deploy tokenA with <18 decimals and wrap 100 units via wrapperFactory.wrap.
2. Add liquidity ETH with wrappedTokenA to create a WETH/wrappedTokenA pair.
3. As user0, approve masterRouter for 550 tokenA and call `masterRouter.swapExactTokensForETH(tokenA, 550, 0, [wrappedTokenA, WETH], user0, deadline)`.
4. Observe wrappedTokenA balance of masterRouter increases by the unswapped wrapped amount (550 * decimalOffset - 550).
5. Any attacker calls `masterRouter.swapExactTokensForTokens` with wrappedTokenIn; it swaps `IERC20(wrappedTokenIn).balanceOf(address(this))`, sweeping the stuck tokens.
**How found:** Code review of swapExactTokensForETH noticed amountIn is passed unchanged to JalaRouter02 despite _approveAndWrap wrapping it, implying a decimal-offset mismatch; confirmed with a Foundry test logging router wrapped-token balance before and after.
**Exploitation:** Attacker monitors the master router for wrapped token balances left by users calling swapExactTokensForETH, then calls swapExactTokensForTokens which uses the full contract balance as input, converting the stuck wrapped tokens to profit.
**Insight:** In routers that wrap tokens to normalize decimals, check every downstream call for the wrapped amount vs the original amount; any function using balanceOf(address(this)) as swap input turns leftover dust into a stealable pool.
