Open code423n4 opened 2 years ago
Dup of #442
Making Primary as the most thorough
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
GalloDaSballo marked the issue as selected for report
GalloDaSballo marked the issue as primary issue
Marked this issue as Satisfactory as requested by @GalloDaSballo
Revert, wrong action.
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 givenamountOut
. This function will check the pair of_token
and_tokenNext
isJoePair
orLBPair
using_binStep
.If
_binStep == 0
, it will be aJoePair
otherwise it will be anLBPair
.As we can see when
_binStep == 0
and_token < _tokenPath[i]
(in another word we swap throughJoePair
and pair'stoken0
is_token
andtoken1
is_tokenPath[i]
), it willreserveIn
,reserveOut
)_amountIn
by using the formulaBut 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:
_reserveIn (rIn)
: reserve of_token
in pair_reserveOut (rOut)
: reserve of_tokenPath[i]
in pairamountOut_
: the amount of_tokenPath
the user wants to gainOutput:
rAmountIn
: the actual amount of_token
we need to transfer to the pair.Generate Formula Cause
JoePair
takes 0.3% ofamountIn
as fee, we getamountInDeductFee = amountIn' * 0.997
Following the constant product formula, we have
As we can see
rAmountIn
is different fromamountsIn[i - 1]
, the denominator ofrAmountIn
is(rOut - amountOut_) * 997
when the denominator ofamountsIn[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 fromJoePair
and 0 pool fromLBPair
.amountsIn
from function_getAmountsIn
. SoamountsIn
will be [incorrectAmountIn
,userDesireAmount
].incorrectAmountIn
to_pairs[0]
to prepare for the swap._swapTokensForExactToken
to execute the swap.In this step it will reach to line 841 which will set the expected
amountOut = amountsIn[i+1] = amountsIn[1] = userDesireAmount
.So after calling
IJoePair(_pair).swap()
, the user just gets exactlyamountOut
and wastes a lot of tokenIn that (s)he transfers to the pool.Proof of concept
Here is our test script to describe the impacts
You can place this file into
/test
folder and run it usingExplanation of test script: (For more detail u can read the comments from test script above)
WAVAX/USDC
was around 15.57. We try to use LBRouter functionswapTokensForExactTokens
to swap 10$ WAVAX (10e18 wei) to 1$ USDC (1e6 wei). But it reverts with the errorLBRouter__MaxAmountInExceeded
. But when we swap directly to JoePair, it swap successfully 10$ AVAX (10e18 wei) to 155$ USDC (155e6 wei).swapTokensForExactTokens
again with very largeamountInMax
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