code-423n4 / 2022-10-traderjoe-findings

2 stars 0 forks source link

Wrong calculation in function `LBRouter._getAmountsIn` make user lose a lot of tokens when swap through JoePair (most of them will gifted to JoePair freely) #400

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

Lines of code

https://github.com/code-423n4/2022-10-traderjoe/blob/79f25d48b907f9d0379dd803fc2abc9c5f57db93/src/LBRouter.sol#L725

Vulnerability details

Vulnerable detail

Function LBRouter._getAmountsIn is a helper function to return the amounts in with given amountOut. This function will check the pair of _token and _tokenNext is JoePair or LBPair using _binStep.

But unfortunately the denominator _reserveOut - amountOut_ * 997 seem incorrect. It should be (_reserveOut - amountOut_) * 997. We will do some math calculations here to prove the expression above is wrong.

Input:

Output:

Generate Formula Cause JoePair takes 0.3% of amountIn as fee, we get

Following the constant product formula, we have

    rIn * rOut = (rIn + amountInDeductFee) * (rOut - amountOut_)
==> rIn + amountInDeductFee = rIn * rOut / (rOut - amountOut_) + 1
<=> amountInDeductFee = (rIn * rOut) / (rOut - amountOut_) - rIn + 1
<=> rAmountIn * 0.997 = rIn * amountOut / (rOut - amountOut_) + 1
<=> rAmountIn = (rIn * amountOut * 1000) / ((rOut - amountOut_) * 997) + 1
<=> 

As we can see rAmountIn is different from amountsIn[i - 1], the denominator of rAmountIn is (rOut - amountOut_) * 997 when the denominator of amountsIn[i - 1] is _reserveOut - amountOut_ * 997 (Missing one bracket)

Impact

Loss of fund: User will send a lot of tokenIn (much more than expected) but just gain exact amountOut in return.

Let dive in the function swapTokensForExactTokens() to figure out why this scenario happens. I will assume I just swap through only one pool from JoePair and 0 pool from LBPair.

Proof of concept

Here is our test script to describe the impacts

You can place this file into /test folder and run it using

forge test --match-test testBugSwapJoeV1PairWithLBRouter --fork-url https://rpc.ankr.com/avalanche --fork-block-number 21437560 -vv

Explanation of test script: (For more detail u can read the comments from test script above)

  1. Firstly we get the Joe v1 pair WAVAX/USDC from JoeFactory.
  2. At the forked block, price WAVAX/USDC was around 15.57. We try to use LBRouter function swapTokensForExactTokens to swap 10$ WAVAX (10e18 wei) to 1$ USDC (1e6 wei). But it reverts with the error LBRouter__MaxAmountInExceeded. But when we swap directly to JoePair, it swap successfully 10$ AVAX (10e18 wei) to 155$ USDC (155e6 wei).
  3. We use LBRouter function swapTokensForExactTokens again with very large amountInMax to swap 1$ USDC (1e6 wei). It swaps successfully but needs to pay a very large amount WAVAX (much more than price).

Tools Used

Foundry

Recommended Mitigation Steps

Modify function LBRouter._getAmountsIn as follow

// url = https://github.com/code-423n4/2022-10-traderjoe/blob/79f25d48b907f9d0379dd803fc2abc9c5f57db93/src/LBRouter.sol#L717-L728
if (_binStep == 0) {
    (uint256 _reserveIn, uint256 _reserveOut, ) = IJoePair(_pair).getReserves();
    if (_token > _tokenPath[i]) {
        (_reserveIn, _reserveOut) = (_reserveOut, _reserveIn);
    }

    uint256 amountOut_ = amountsIn[i];
    // Legacy uniswap way of rounding
    // Fix here 
    amountsIn[i - 1] = (_reserveIn * amountOut_ * 1_000) / ((_reserveOut - amountOut_) * 997) + 1;
} else {
    (amountsIn[i - 1], ) = getSwapIn(ILBPair(_pair), amountsIn[i], ILBPair(_pair).tokenX() == _token);
}
trust1995 commented 2 years ago

Dup of #442

GalloDaSballo commented 1 year ago

Making Primary as the most thorough

GalloDaSballo commented 1 year ago

The warden has shown how, due to an incorrect order of operation, the math for the router will be incorrect.

While the error could be considered a typo, the router is the designated proper way of performing a swap, and due to this finding, the math will be off.

Because the impact shows an incorrect logic, and a broken invariant (the router uses incorrect amounts, sometimes reverting, sometimes costing the end user more tokens than necessary), I believe High Severity to be appropriate.

Mitigation will require refactoring and may be aided by the test case offered in this report

c4-judge commented 1 year ago

GalloDaSballo marked the issue as selected for report

c4-judge commented 1 year ago

GalloDaSballo marked the issue as primary issue

Simon-Busch commented 1 year ago

Marked this issue as Satisfactory as requested by @GalloDaSballo

Simon-Busch commented 1 year ago

Revert, wrong action.