First Flight #18: T-Swap

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

The initial deposit can be frontrunned to grief the pool making it useless

[M-01] The initial deposit can be frontrunned to grief the pool making it useless

Description:
An attacker can grief the pool contracts by front-running the first deposit and passing zero for maximumPoolTokensToDeposit. This will render the pool useless because no one else can use the deposit function to fill the needed poolTokens. Since in getPoolTokensToDepositBasedOnWeth, the poolTokenReserves would always be zero unless the initial depositor transfers the poolTokens directly to the contract. But this itself gives an incentive to the griefer because he can withdraw and get compensation after the direct transfer was done.

function getPoolTokensToDepositBasedOnWeth(
uint256 wethToDeposit
) public view returns (uint256) {
@> uint256 poolTokenReserves = i_poolToken.balanceOf(address(this));
uint256 wethReserves = i_wethToken.balanceOf(address(this));
return (wethToDeposit * poolTokenReserves) / wethReserves;
}

Impact:
The impact of this vulnerability is significant as it allows an attacker to prevent legitimate users from participating in the pool by front-running the initial deposit. By doing so, the attacker can ensure that the pool remains underutilized or completely unusable for others who wish to deposit into it. This attack not only disrupts the intended functionality of the pool but also undermines trust in the platform, potentially leading to financial losses for those who attempt to interact with the compromised pool. Additionally, the attacker can exploit this situation to extract value from the system at the expense of legitimate participants, thereby gaining an unfair advantage.

Proof of Concept:
Add the following test to the existing test suite. Exploit steps:

  1. The attacker waits for a createPool transaction in the mempool.

  2. After finding one, they front-run or simply deposit some ETH and zero poolTokens before the actual pool creator intends to deposit.

  3. LP can't deposit poolTokens via deposit function; LP has to transfer funds directly to run the pool.

  4. Attacker withdraws getting more than his initial balance in PoolToken and losing no WETH.

function testInitialDepositCanBeFrontrunned() public {
// Attacler deposits some eth and 0 pool token
vm.startPrank(user);
weth.approve(address(pool), 10e18);
poolToken.approve(address(pool), 10e18);
pool.deposit(1e9 + 1, 0, 0, uint64(block.timestamp));
// lp cant deposit poolTokens via deposit function
vm.startPrank(liquidityProvider);
weth.approve(address(pool), 100e18);
poolToken.approve(address(pool), 100e18);
uint256 depositedPtoken = pool.deposit(
100e18,
100e18,
100e18,
uint64(block.timestamp)
);
assertEq(poolToken.balanceOf(address(pool)),0);
// lp has to transfer fund directly to run the pool
poolToken.transfer(address(pool), 100e18);
// attacker withdraws getting more than his initial balance in PoolToken and loosing no wet
vm.startPrank(user);
pool.withdraw(pool.balanceOf(user), 1e9, 1e9, uint64(block.timestamp));
assertEq(weth.balanceOf(user), 10e18);
assert(poolToken.balanceOf(user) > 10e18);
}

Recommended Mitigation:
Set a minimum deposit for pool token and check for it in the initial deposit. This way, even if the attacker front-runs the initial deposit, they won't gain anything from it. Note that this is just a simple example, and the real implementation should probably get the minimum initial pool token amount in the constructor.

function deposit(
uint256 wethToDeposit,
uint256 minimumLiquidityTokensToMint,
uint256 maximumPoolTokensToDeposit,
uint64 deadline //@audit-wriiten as high, med this does not check for deadline makes frontrunning attacks possible.
)
external
revertIfZero(wethToDeposit)
returns (uint256 liquidityTokensToMint)
{
.
.
.
} else {
+ require(i_poolToken.balanceOf(address(this)) >0 , "some custom error");
// This will be the "initial" funding of the protocol. We are starting from blank here!
// We just have them send the tokens in, and we mint liquidity tokens based on the weth
_addLiquidityMintAndTransfer(
wethToDeposit,
maximumPoolTokensToDeposit,
wethToDeposit
);
liquidityTokensToMint = wethToDeposit;
}
}
Updates

Appeal created

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

_addLiquidityMintAndTransfer function does not check if poolTokensToDeposit is zero.

Support

FAQs

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