Summary
The deposit function in the TSwapPool contract lacks a deadline check, which is a crucial parameter for ensuring that the transaction is executed within a specific timeframe. The absence of this check exposes the function to potential timing-related vulnerabilities.
Vulnerability Details
In decentralized finance (DeFi) protocols, adding liquidity typically involves interacting with the pool by supplying tokens. Transactions in blockchain networks can experience delays due to network congestion or other factors. A deadline parameter is used to ensure that a transaction is only executed within a specific time window, protecting users from executing a transaction at an unfavorable time or price.
function deposit(
uint256 wethToDeposit,
uint256 minimumLiquidityTokensToMint,
uint256 maximumPoolTokensToDeposit,
uint64 deadline
)
external
revertIfZero(wethToDeposit)
// no check for 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));
uint256 poolTokenReserves = i_poolToken.balanceOf(address(this));
uint256 poolTokensToDeposit = getPoolTokensToDepositBasedOnWeth(wethToDeposit)
if (maximumPoolTokensToDeposit < poolTokensToDeposit) {
revert TSwapPool__MaxPoolTokenDepositTooHigh(maximumPoolTokensToDeposit, poolTokensToDeposit);
}
liquidityTokensToMint = (wethToDeposit * totalLiquidityTokenSupply()) / wethReserves
if (liquidityTokensToMint < minimumLiquidityTokensToMint) {
revert TSwapPool__MinLiquidityTokensToMintTooLow(minimumLiquidityTokensToMint, liquidityTokensToMint);
}
_addLiquidityMintAndTransfer(wethToDeposit, poolTokensToDeposit, liquidityTokensToMint);
} else {
_addLiquidityMintAndTransfer(wethToDeposit, maximumPoolTokensToDeposit, wethToDeposit);
liquidityTokensToMint = wethToDeposit
}
}
Without a deadline check, users might:
Submit a transaction that gets delayed and executed at a later time when market conditions have changed.
Face slippage and unfavorable rates, leading to potential losses.
Be exposed to front-running or other timing-related attacks.
Impact
User Losses: Users might add liquidity at a much worse rate than expected, leading to financial losses.
Market Manipulation: Malicious actors can exploit the lack of a deadline to manipulate the market, ensuring transactions occur at times beneficial to them.
Increased Risk of Attacks: The system becomes more vulnerable to front-running and other timing attacks, compromising the integrity and trustworthiness of the protocol.
Proof of Concept (PoC):
Implement the deposit function without a deadline parameter.
Simulate network congestion or delays.
Observe that the transaction is executed at an unintended time, potentially with unfavorable conditions.
Tools Used
Manual Review
Recommendations
Validate Deadline in Function: Before executing the deposit function, validate that the current block timestamp is less than or equal to the provided deadline.
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));
uint256 poolTokenReserves = i_poolToken.balanceOf(address(this));
uint256 poolTokensToDeposit = getPoolTokensToDepositBasedOnWeth(wethToDeposit);
if (maximumPoolTokensToDeposit < poolTokensToDeposit) {
revert TSwapPool__MaxPoolTokenDepositTooHigh(maximumPoolTokensToDeposit, poolTokensToDeposit);
}
// We do the same thing for liquidity tokens. Similar math.
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
_addLiquidityMintAndTransfer(wethToDeposit, maximumPoolTokensToDeposit, wethToDeposit);
liquidityTokensToMint = wethToDeposit;
}
}