Assessment reports>Beefy UniswapV3>High findings>Calm period not checked in all functions
Category: Coding Mistakes

Calm period not checked in all functions

High Severity
Medium Impact
Medium Likelihood

Description

The onlyCalmPeriods modifier is a critical safety component of the strategy, as it is used to prevent deposits and harvests when the current price is not within 1% of the time-weighted average price (TWAP) over the last 60 seconds. This is to protect against price manipulation by an attacker during their transaction, possibly using flash loans or other means.

/// @notice Modifier to only allow deposit/harvest actions when current price is within 1% of twap.
modifier onlyCalmPeriods() {
    int24 tick = currentTick();
    int56 twapTick = twap();

    int56 upperBound = deviation + deviationThreshold;
    int56 lowerBound = deviationThreshold - deviation;

    // Calculate if greater than deviation % from twap and revert if it is. 
    if (tick >= 0) {
        if (tick > twapTick * upperBound / deviationThreshold || tick < twapTick * lowerBound / deviationThreshold) revert NotCalm();
    } else if (tick < twapTick * upperBound / deviationThreshold || tick > twapTick * lowerBound / deviationThreshold) revert NotCalm();
    _;
}

One of the harvest functions as well as withdraw do not check whether the system is in a calm period or not. This means that users can still interact with the system, even though it is not safe to do so. This can lead to unexpected behavior and potential losses for the users.

function harvest() external {
    _harvest(tx.origin);
}

function withdraw(uint256 _amount0, uint256 _amount1) external {
    _onlyVault();

    // Liquidity has already been removed in beforeAction() so this is just a simple withdraw.
    if (_amount0 > 0) IERC20Metadata(lpToken0).safeTransfer(vault, _amount0);
    if (_amount1 > 0) IERC20Metadata(lpToken1).safeTransfer(vault, _amount1);

    // After we take what is needed we add it all back to our positions. 
    if (!paused()) _addLiquidity();

    (uint256 bal0, uint256 bal1) = balances();

    // TVL Balances after withdraw
    emit Withdraw(bal0, bal1);
}

Impact

This issue can lead to unexpected slippage for the users as well as potential losses for the protocol as a whole.

Recommendations

We recommend ensuring that the onlyCalmPeriods modifier is used in all the external/public functions that can be called by anyone or by the vault. This will prevent users from interacting with the system when it is not safe to do so.

function harvest() 
    external 
+   onlyCalmPeriods 
{
    _harvest(tx.origin);
}

function withdraw(uint256 _amount0, uint256 _amount1) 
    external 
+   onlyCalmPeriods    
{
    _onlyVault();

    // Liquidity has already been removed in beforeAction() so this is just a simple withdraw.
    if (_amount0 > 0) IERC20Metadata(lpToken0).safeTransfer(vault, _amount0);
    if (_amount1 > 0) IERC20Metadata(lpToken1).safeTransfer(vault, _amount1);

    // After we take what is needed we add it all back to our positions. 
    if (!paused()) _addLiquidity();

    (uint256 bal0, uint256 bal1) = balances();

    // TVL Balances after withdraw
    emit Withdraw(bal0, bal1);
}

Remediation

This issue has been acknowledged by Beefy, and fixes were implemented in the following commits:

Zellic © 2024Back to top ↑