First Flight #18: T-Swap

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

The `TSwapPool::swapExactOutput` Misses Input and Check for `maxInputAmount`

[H-03] The TSwapPool::swapExactOutput Misses Input and Check for maxInputAmount

Description:
The swapExactOutput function lacks a slippage control input parameter, which is essential for protecting users from price fluctuations that could occur between the time they submit a transaction and when it gets executed.

function swapExactOutput(
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 = getInputAmountBasedOnOutput(
outputAmount,
inputReserves,
outputReserves
);
_swap(inputToken, inputAmount, outputToken, outputAmount);
}

Impact:
The absence of a check for maxInputAmount in the swapExactOutput method makes it vulnerable to Miner Extractable Value (MEV) attacks. An MEV bot could sandwich any transaction made by this function to gain incentives at the expense of the user. This vulnerability allows attackers to manipulate the price seen by the victim's transaction, forcing them to pay more than they initially intended.

Proof of Concept:
A simple test demonstrates the possibility of sandwich attacks due to the lack of slippage control:

function testSwapExactOutputSandwichAttack() public {
attacker = makeAddr("attacker");
poolToken.mint(attacker, 100e18);
vm.startPrank(attacker);
poolToken.approve(address(pool), 100e18);
weth.approve(address(pool), 24962443665498247371);
// poolToken.mint(user, 10e18);
//liquidity deposit:
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 200e18);
poolToken.approve(address(pool), 200e18);
pool.deposit(50e18, 50e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
//user wants to swap
vm.prank(user);
poolToken.approve(address(pool), 10e18);
// currently there is 100e18 WETH and 50e18 PT in pool
// we want to swap x poolToken for 1e18 WETH
// x considering fees would be = 2_046_957_198_124_987_206
uint256 expected = 2_046_957_198_124_987_206;
//here an attacker swaps poolToken for weth by frontrunning user,
vm.prank(attacker);
pool.swapExactInput(
poolToken,
100e18,
weth,
0,
uint64(block.timestamp)
);
vm.prank(user);
uint256 actual = pool.swapExactOutput(
poolToken,
weth,
1e18,
uint64(block.timestamp)
);
//here the attacker swaps weth for PT,
vm.startPrank(attacker);
pool.swapExactInput(
weth,
weth.balanceOf(attacker),
poolToken,
0,
uint64(block.timestamp)
);
assert(actual > expected);
assert(weth.balanceOf(attacker) >= 0);
assert(poolToken.balanceOf(attacker) > 100e18); // attacker had 0 weth and 100e18 PT initially check if he has more now
// ans. 105_982_517_697_077_211_812 i.e. attacker gained 5.98 e18 PT by doing this
}

Recommended Mitigation:
Introduce slippage control by adding a maxInputAmount parameter to the function. This parameter will allow users to specify the maximum amount they are willing to pay for the output tokens, thereby protecting them from excessive price movements caused by MEV attacks.

+ error TSwapPool__OutputTooHigh(uint256 actual, uint256 max);
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 TSwapPool__OutputTooHigh(inputAmount,maxInputAmount)
+ }
_swap(inputToken, inputAmount, outputToken, outputAmount);
}
Updates

Appeal created

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