First Flight #18: T-Swap

First Flight #18
Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Lack of slippage protection in `TSwapPool::swapExactOutput` causes users to potentially receive way fewer tokens

Description: The swapExactOutput function does not include any sort of slippage protection. This function is similar to what is done in TSwapPool::swapExactInput, where the function specifies a minOutputAmount, the swapExactOutput function should specify a maxInputAmount.

Impact: If market conditions change before the transaciton processes, the user could get a much worse swap.

Proof of Concept:

  1. The price of 1 WETH right now is 1,000 USDC

  2. User inputs a swapExactOutput looking for 1 WETH
    (i) inputToken = USDC
    (ii) outputToken = WETH
    (iii) outputAmount = 1
    (iv) deadline = whatever

  3. The function does not offer a maxInput amount

  4. As the transaction is pending in the mempool, the market changes! And the price moves HUGE -> 1 WETH is
    now 10,000 USDC. 10x more than the user expected

  5. The transaction completes, but the user sent the protocol 10,000 USDC instead of the expected 1,000 USDC

POC
​
function testInvariantBroken() public {
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
​
uint256 outputWeth = 1e17;
​
vm.startPrank(user);
poolToken.approve(address(pool), type(uint256).max);
poolToken.mint(user, 100e18);
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
​
int256 startingY = int256(weth.balanceOf(address(pool)));
int256 expectedDeltaY = int256(-1) * int256(outputWeth);
​
pool.swapExactOutput(poolToken, weth, outputWeth, uint64(block.timestamp));
vm.stopPrank();
​
uint256 endingY = weth.balanceOf(address(pool));
int256 actualDeltaY = int256(endingY) - int256(startingY);
assertEq(actualDeltaY, expectedDeltaY);
}

Recommended Mitigation: We should include a maxInputAmount so the user only has to spend up to a
specific amount, and can 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);
Updates

Appeal created

inallhonesty Lead Judge over 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of slippage protection in `TSwapPool::swapExactOutput` causes users to potentially receive way fewer tokens

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!