Summary
The swapExactOutput
function does not include any sort of slippage protection. This function is similar to TSwapPool::swapExactInput
, where the function has a minOutputAmount
. TheswapExactOutput
function should have a maxInputAmount
.
Vulnerability Details
If market conditions change in the pool before the users transaction goes through, then the user could get a much less favorable swap than they expected.
Impact
Conceptually this is a scenario that could happen
1. The price of 1 WETH is equal to 1,000 USDC
2. User inputs a `swapExactOutput` looking for 1 WETH (Meaning they would pay 1,000 USDC)
1. inputToken = USDC
2. outputToken = WETH
3. outputAmount = 1
4. deadline = whenever
3. The `swapExactOutput` function does not offer a maxInput amount
4. As the transaction is pending in the mempool, the market value changes and there is a price movement, 1 WETH is equal to 5,000 USDC
(Much more than the user expected)
5. The users transaction completes, but now it cost the user 5,000 USDC instead of their expected 1,000 USDC
function testSwapExactOutput() public {
uint256 outputWeth = 1e18;
vm.startPrank(user);
poolToken.approve(address(pool), type(uint256).max);
poolToken.mint(user, 100e18);
vm.stopPrank();
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 500e18);
poolToken.approve(address(pool), 500e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
vm.startPrank(user);
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
vm.stopPrank();
assert(poolToken.balanceOf(user) < 100e18);
}
Tools Used
--Foundry
Recommendations
It is recommended to include a maxInputAmount
so the user only has to spend up to that amount. This helps them predict how much they will spend on the protocol.
function swapExactOutput(
IERC20 inputToken,
+ uint256 maxInputAmount,
.
.
.
inputAmount = getInputAmountBasedOnOutput(outputAmount, inputReserves, outputReserves);
+ if(inputAmount > maxInputAmount){
+ revert();
+ }
_swap(inputToken, inputAmount, outputToken, outputAmount);