First Flight #18: T-Swap

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

Incorrect fee calculation in `TSwapPool::getInputAmountBasedOnOutput` causes protocll to take too many tokens from users, resulting in lost fees

Description: The getInputAmountBasedOnOutput function is intended to calculate the amount of tokens a user should deposit given an amount of tokens of output tokens. However, the function currently miscalculates the resulting amount. When calculating the fee, it scales the amount by 10_000 instead of 1_000.

Impact: Protocol takes more fees than expected from users.

Proof of Concept: To test this, include the following code in the TSwapPool.t.sol file:

POC
function testFlawedSwapExactOutput() public {
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
pool.deposit(100e18, 100e18, 100e18, uint64(block.timestamp));
vm.stopPrank();
vm.startPrank(user);
uint256 expected = 9e18;
poolToken.approve(address(pool), 10e18);
pool.swapExactInput(poolToken, 10e18, weth, expected, uint64(block.timestamp));
vm.stopPrank();
vm.startPrank(liquidityProvider);
pool.approve(address(pool), 100e18);
pool.withdraw(100e18, 90e18, 100e18, uint64(block.timestamp));
assertEq(pool.totalSupply(), 0);
assert(weth.balanceOf(liquidityProvider) + poolToken.balanceOf(liquidityProvider) > 400e18);
}
function testFlawedSwapExactOutput() public {
uint256 initialLiquidity = 100e18;
vm.startPrank(liquidityProvider);
console.log("liquidityProvider weth balance before deposit:", weth.balanceOf(address(liquidityProvider)));
console.log(
"liquidityProvider poolToken balance after deposit:", poolToken.balanceOf(address(liquidityProvider))
);
weth.approve(address(pool), initialLiquidity);
poolToken.approve(address(pool), initialLiquidity);
pool.deposit({
wethToDeposit: initialLiquidity,
minimumLiquidityTokensToMint: 0,
maximumPoolTokensToDeposit: initialLiquidity,
deadline: uint64(block.timestamp)
});
console.log("liquidityProvider weth balance after deposit:", weth.balanceOf(address(liquidityProvider)));
console.log("liquidityProvider poolToken balance after deposit:", weth.balanceOf(address(liquidityProvider)));
vm.stopPrank();
// User has 11 pool tokens
address someUser = makeAddr("someUser");
uint256 userInitialPoolTokenBalance = 11e18;
poolToken.mint(someUser, userInitialPoolTokenBalance);
console.log("someUser poolToken balance before withdrew:", poolToken.balanceOf(address(someUser)));
vm.startPrank(someUser);
// Users buys 1 WETH from the pool, paying with pool tokens
poolToken.approve(address(pool), type(uint256).max);
pool.swapExactOutput(poolToken, weth, 1 ether, uint64(block.timestamp));
assertLt(poolToken.balanceOf(someUser), 1 ether);
vm.stopPrank();
vm.startPrank(liquidityProvider);
pool.withdraw(
pool.balanceOf(liquidityProvider),
1, // minWethToWithdraw
1, // minPoolTokensToWithdraw
uint64(block.timestamp)
);
console.log("liquidityProvider weth balance after withdrew:", weth.balanceOf(address(liquidityProvider)));
console.log(
"liquidityProvider poolToken balance after withdrew:", poolToken.balanceOf(address(liquidityProvider))
);
assertEq(weth.balanceOf(address(pool)), 0);
assertEq(poolToken.balanceOf(address(pool)), 0);
console.log("someUser poolToken balance after withdrew:", poolToken.balanceOf(address(someUser)));
}

Recommended Mitigation:

function getInputAmountBasedOnOutput(
uint256 outputAmount,
uint256 inputReserves,
uint256 outputReserves
)
public
pure
revertIfZero(outputAmount)
revertIfZero(outputReserves)
returns (uint256 inputAmount)
{
- return ((inputReserves * outputAmount) * 10_000) / ((outputReserves - outputAmount) * 997);
+ return ((inputReserves * outputAmount) * 1_000) / ((outputReserves - outputAmount) * 997);
}
Updates

Appeal created

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Incorrect fee calculation in TSwapPool::getInputAmountBasedOnOutput causes protocol to take too many tokens from users, resulting in lost fees

Support

FAQs

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