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 spend more tokens in return of exact ouput 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 transaction processes, the user could get a much worse swap and end sending more input tokens for the same exact ouput which could have been swapped with relatively less input tokens.

Proof of Concept:

function testLackOfSlippageProtection() public{
//We start by adding liquidity in the protocol
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
//Note the balance user beofore the first swap
uint256 userBalanceBeforeFirstSwap = poolToken.balanceOf(user);
//user makes a swap by getting 9e18 weth in exchange of poolTokens
vm.startPrank(user);
uint256 expectedTwo = 9e18;
poolToken.approve(address(pool), 100e18);
pool.swapExactOutput(poolToken, weth, expectedTwo, uint64(block.timestamp));
vm.stopPrank();
//we take note of ending poolToken balance of user
uint256 userBalancAfterFirstSwap = poolToken.balanceOf(user);
//we calculate the poolTokens spent in swapping of 9e18 weth
uint256 deltaFirst = userBalanceBeforeFirstSwap - userBalancAfterFirstSwap;
//we change the exchange rate or disrupt the market by adding more poolTokens in the protocol compared to weth so now more poolTokens are needed in exchange for 1 weth.
vm.startPrank(liquidityProviderTwo);
weth.approve(address(pool), 3000e18);
poolToken.approve(address(pool), 3000e18);
pool.deposit(100e18, 1e18, 3000e18, uint64(block.timestamp));
vm.stopPrank();
//user then swaps again to get same 9e18 weth in exchange of poolTokens
vm.startPrank(user);
uint256 expectedThree = 9e18;
poolToken.approve(address(pool), 300e18);
pool.swapExactOutput(poolToken, weth, expectedThree, uint64(block.timestamp));
vm.stopPrank();
//we take note of user's poolTokens spent in exchange of 9e18 weth for the second time.
uint256 userBalanceAfterSecondSwap = poolToken.balanceOf(user);
uint256 deltaSecond = userBalancAfterFirstSwap - userBalanceAfterSecondSwap;
//we can compare that more poolTokens were spent in the second swap compared to the first even though user got same amount of weth 9e18
assert(deltaFirst < deltaSecond);
}

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,
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.