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`

Summary

The function swapExactOutput lacks slippage protection, which means that once the user initiates a transaction, it will be completed even if the price fluctuates beyond the user's acceptable range during this period. This can lead to financial losses for the user and a poor user experience.

Vulnerability Details

The implementation of swapExactOutput lacks verification of the final transaction price range, resulting in unpredictable transaction prices that are likely to deviate significantly from the user's expectations.

Proof Of Code

First, we need to fix the price calculation bug that existed in the original swapExactOutput function. Here we replace the call to the getInputAmountBasedOnOutput function with the correct calculation.

Place the following into TSwapPool.sol, we will use this function in the test:

function swapExactOutputFixed(
IERC20 inputToken,
IERC20 outputToken,
uint256 outputAmount,
uint64 deadline
)
public
revertIfZero(outputAmount)
revertIfDeadlinePassed(deadline)
returns (uint256 inputAmount)
{
uint256 inputReserves = inputToken.balanceOf(address(this));
uint256 outputReserves = outputToken.balanceOf(address(this));
inputAmount = ((inputReserves * outputAmount) * 1_000) / ((outputReserves - outputAmount) * 997)
_swap(inputToken, inputAmount, outputToken, outputAmount);
}

The above function is provided for testing convenience only. Please delete it after confirming the correction.

To simulate a scenario where a large transaction causes significant price fluctuations, and the user, being unaware of this, expects the previous price but ends up paying much more tokens than anticipated.

Place the following into TSwapPool.t.sol:

function testSwapSlippageProtection() public {
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
address trader = makeAddr("trader");
weth.mint(trader, 100e18);
poolToken.mint(trader, 1000e18);
vm.startPrank(trader);
poolToken.approve(address(pool), type(uint256).max);
uint256 expectWeth = 90e18;
pool.swapExactOutputFixed(poolToken, weth, expectWeth, uint64(block.timestamp));
// The trader should swap 900e18 poolTokens for 90e18 WETH,
// resulting in the pool having 1000e18 poolTokens and 10e18 WETH.
// However, the user assumes the pool still has 100e18 poolTokens and 100e18 WETH.
// Thus, the user thinks they can use 5e18 poolTokens to get 5e18 WETH.
// In reality, the user would actually need to pay 1000e18 poolTokens,
// which is significantly beyond the user's expectations.
poolToken.mint(user, 1000e18);
uint256 userStartingBalance = poolToken.balanceOf(user);
vm.startPrank(user);
poolToken.approve(address(pool), type(uint256).max);
pool.swapExactOutputFixed(poolToken, weth, 5e18, uint64(block.timestamp));
uint256 userCost = userStartingBalance - poolToken.balanceOf(user);
assert(userCost > 5e18 * 200);
}

Impact

In the event of severe market price fluctuations, it is highly likely that transactions will be executed at extremely unfavorable prices, which are unacceptable for users.

Tools Used

Recommendations

The function needs to include a maxInputAmount variable to ensure that the transaction occurs within a specified range acceptable to the user.

function swapExactOutput(
IERC20 inputToken,
IERC20 outputToken,
uint256 outputAmount,
+ uint256 maxInputAmount
uint64 deadline
)
public
revertIfZero(outputAmount)
revertIfDeadlinePassed(deadline)
returns (uint256 inputAmount)
{
uint256 inputReserves = inputToken.balanceOf(address(this));
uint256 outputReserves = outputToken.balanceOf(address(this));
inputAmount = getInputAmountBasedOnOutput(outputAmount, inputReserves, outputReserves);
+ if(inputAmount > maxInputAmount) {
+ revert();
+ }
_swap(inputToken, inputAmount, outputToken, outputAmount);
}
Updates

Appeal created

inallhonesty Lead Judge 11 months 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.