First Flight #18: T-Swap

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

`TSwapPool.sol::deposit` doesn't use deadline param.

Summary

TSwapPool.sol::deposit doesn't use deadline param.

Vulnerability Details

TSwapPool.sol::deposit deadline param isn't used anywhere in the function.

Impact

  1. For any reason e.g a transaction fee being too low for miners to want to execute the transaction, the transaction may sit in the mempool for a longer time than is stated by the user when calling the function. This means the transaction could be executed past the deadline stated by the user. This may be undesirable for the user. Furthermore, the TSwapPool.sol::deposit liquidityTokensToMint may change over this time, and could result in more slippage i.e the liquidityTokensToMint is closer to minimumLiquidityTokensToMint.

  2. MEV bots can front run your transaction and influence the liquidity tokens to mint which means more slippage for you. The bot can then take some of that additional slippage as a profit. However, you cannot get less than minimumLiquidityTokensToMint, so I've classified this as a medium.

Tools Used

Solidity compiler

Recommendations

Add a check to see if the deadline has already passed.

function deposit(
uint256 wethToDeposit,
uint256 minimumLiquidityTokensToMint,
uint256 maximumPoolTokensToDeposit,
uint64 deadline
)
external
revertIfZero(wethToDeposit)
+ revertIfDeadlinePassed(deadline)
returns (uint256 liquidityTokensToMint)
{
if (wethToDeposit < MINIMUM_WETH_LIQUIDITY) {
revert TSwapPool__WethDepositAmountTooLow(MINIMUM_WETH_LIQUIDITY, wethToDeposit);
}
if (totalLiquidityTokenSupply() > 0) {
uint256 wethReserves = i_wethToken.balanceOf(address(this));
// @audit unused poolTokenReserves
uint256 poolTokenReserves = i_poolToken.balanceOf(address(this));
// Our invariant says weth, poolTokens, and liquidity tokens must always have the same ratio after the
// initial deposit
// poolTokens / constant(k) = weth
// weth / constant(k) = liquidityTokens
// aka...
// weth / poolTokens = constant(k)
// To make sure this holds, we can make sure the new balance will match the old balance
// (wethReserves + wethToDeposit) / (poolTokenReserves + poolTokensToDeposit) = constant(k)
// (wethReserves + wethToDeposit) / (poolTokenReserves + poolTokensToDeposit) =
// (wethReserves / poolTokenReserves)
//
// So we can do some elementary math now to figure out poolTokensToDeposit...
// (wethReserves + wethToDeposit) / poolTokensToDeposit = wethReserves
// (wethReserves + wethToDeposit) = wethReserves * poolTokensToDeposit
// (wethReserves + wethToDeposit) / wethReserves = poolTokensToDeposit
uint256 poolTokensToDeposit = getPoolTokensToDepositBasedOnWeth(wethToDeposit);
if (maximumPoolTokensToDeposit < poolTokensToDeposit) {
revert TSwapPool__MaxPoolTokenDepositTooHigh(maximumPoolTokensToDeposit, poolTokensToDeposit);
}
// We do the same thing for liquidity tokens. Similar math.
// @audit check this math. lp tokens are minted based on weth, not pool tokens?
liquidityTokensToMint = (wethToDeposit * totalLiquidityTokenSupply()) / wethReserves;
if (liquidityTokensToMint < minimumLiquidityTokensToMint) {
revert TSwapPool__MinLiquidityTokensToMintTooLow(minimumLiquidityTokensToMint, liquidityTokensToMint);
}
_addLiquidityMintAndTransfer(wethToDeposit, poolTokensToDeposit, liquidityTokensToMint);
} else {
// 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
// @audit same param twice. Third param should be liquidityTokensToMint?
_addLiquidityMintAndTransfer(wethToDeposit, maximumPoolTokensToDeposit, wethToDeposit);
liquidityTokensToMint = wethToDeposit;
}
}
function deposit(
uint256 wethToDeposit,
uint256 minimumLiquidityTokensToMint,
uint256 maximumPoolTokensToDeposit,
uint64 deadline
)
external
revertIfZero(wethToDeposit)
returns (uint256 liquidityTokensToMint)
{
if (wethToDeposit < MINIMUM_WETH_LIQUIDITY) {
revert TSwapPool__WethDepositAmountTooLow(MINIMUM_WETH_LIQUIDITY, wethToDeposit);
}
if (totalLiquidityTokenSupply() > 0) {
uint256 wethReserves = i_wethToken.balanceOf(address(this));
// @audit unused poolTokenReserves
uint256 poolTokenReserves = i_poolToken.balanceOf(address(this));
// Our invariant says weth, poolTokens, and liquidity tokens must always have the same ratio after the
// initial deposit
// poolTokens / constant(k) = weth
// weth / constant(k) = liquidityTokens
// aka...
// weth / poolTokens = constant(k)
// To make sure this holds, we can make sure the new balance will match the old balance
// (wethReserves + wethToDeposit) / (poolTokenReserves + poolTokensToDeposit) = constant(k)
// (wethReserves + wethToDeposit) / (poolTokenReserves + poolTokensToDeposit) =
// (wethReserves / poolTokenReserves)
//
// So we can do some elementary math now to figure out poolTokensToDeposit...
// (wethReserves + wethToDeposit) / poolTokensToDeposit = wethReserves
// (wethReserves + wethToDeposit) = wethReserves * poolTokensToDeposit
// (wethReserves + wethToDeposit) / wethReserves = poolTokensToDeposit
uint256 poolTokensToDeposit = getPoolTokensToDepositBasedOnWeth(wethToDeposit);
if (maximumPoolTokensToDeposit < poolTokensToDeposit) {
revert TSwapPool__MaxPoolTokenDepositTooHigh(maximumPoolTokensToDeposit, poolTokensToDeposit);
}
// We do the same thing for liquidity tokens. Similar math.
// @audit check this math. lp tokens are minted based on weth, not pool tokens?
liquidityTokensToMint = (wethToDeposit * totalLiquidityTokenSupply()) / wethReserves;
if (liquidityTokensToMint < minimumLiquidityTokensToMint) {
revert TSwapPool__MinLiquidityTokensToMintTooLow(minimumLiquidityTokensToMint, liquidityTokensToMint);
}
_addLiquidityMintAndTransfer(wethToDeposit, poolTokensToDeposit, liquidityTokensToMint);
} else {
// 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
// @audit same param twice. Third param should be liquidityTokensToMint?
_addLiquidityMintAndTransfer(wethToDeposit, maximumPoolTokensToDeposit, wethToDeposit);
liquidityTokensToMint = wethToDeposit;
}
}function deposit(
uint256 wethToDeposit,
uint256 minimumLiquidityTokensToMint,
uint256 maximumPoolTokensToDeposit,
uint64 deadline
)
external
revertIfZero(wethToDeposit)
returns (uint256 liquidityTokensToMint)
{
if (wethToDeposit < MINIMUM_WETH_LIQUIDITY) {
revert TSwapPool__WethDepositAmountTooLow(MINIMUM_WETH_LIQUIDITY, wethToDeposit);
}
if (totalLiquidityTokenSupply() > 0) {
uint256 wethReserves = i_wethToken.balanceOf(address(this));
// @audit unused poolTokenReserves
uint256 poolTokenReserves = i_poolToken.balanceOf(address(this));
// Our invariant says weth, poolTokens, and liquidity tokens must always have the same ratio after the
// initial deposit
// poolTokens / constant(k) = weth
// weth / constant(k) = liquidityTokens
// aka...
// weth / poolTokens = constant(k)
// To make sure this holds, we can make sure the new balance will match the old balance
// (wethReserves + wethToDeposit) / (poolTokenReserves + poolTokensToDeposit) = constant(k)
// (wethReserves + wethToDeposit) / (poolTokenReserves + poolTokensToDeposit) =
// (wethReserves / poolTokenReserves)
//
// So we can do some elementary math now to figure out poolTokensToDeposit...
// (wethReserves + wethToDeposit) / poolTokensToDeposit = wethReserves
// (wethReserves + wethToDeposit) = wethReserves * poolTokensToDeposit
// (wethReserves + wethToDeposit) / wethReserves = poolTokensToDeposit
uint256 poolTokensToDeposit = getPoolTokensToDepositBasedOnWeth(wethToDeposit);
if (maximumPoolTokensToDeposit < poolTokensToDeposit) {
revert TSwapPool__MaxPoolTokenDepositTooHigh(maximumPoolTokensToDeposit, poolTokensToDeposit);
}
// We do the same thing for liquidity tokens. Similar math.
// @audit check this math. lp tokens are minted based on weth, not pool tokens?
liquidityTokensToMint = (wethToDeposit * totalLiquidityTokenSupply()) / wethReserves;
if (liquidityTokensToMint < minimumLiquidityTokensToMint) {
revert TSwapPool__MinLiquidityTokensToMintTooLow(minimumLiquidityTokensToMint, liquidityTokensToMint);
}
_addLiquidityMintAndTransfer(wethToDeposit, poolTokensToDeposit, liquidityTokensToMint);
} else {
// 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
// @audit same param twice. Third param should be liquidityTokensToMint?
_addLiquidityMintAndTransfer(wethToDeposit, maximumPoolTokensToDeposit, wethToDeposit);
liquidityTokensToMint = wethToDeposit;
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago

Appeal created

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

`deposit` is missing deadline check causing transactions to complete even after the deadline

Support

FAQs

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