Farseen0/scar-pairs huggingface dataset:
LOW severity: [TOKE-31] New NAV per share calculation when setting streaming fee could be stale. **Severity:** Low

**Path:** AutopoolFees.sol:setStreamingFeeBps

**Description:**

When setting a new streaming fee, the `navPerShareLastFeeMark` is reset to the current share rate so that the new streaming fee is not applied to past profits.

However, it uses the `totalAssets()` function of the...

Vulnerable code:
function setStreamingFeeBps(IAutopool.AutopoolFeeSettings storage feeSettings, uint256 fee) external {
        if (fee >= FEE_DIVISOR) {
            revert InvalidFee(fee);
        }

        feeSettings.streamingFeeBps = fee;

        IAutopool vault = IAutopool(address(this));

        // Set the high mark when we change the fee so we aren't able to go farther back in
        // time than one debt reporting and claim fee's against past profits
        uint256 ts = vault.totalSupply();
        if (ts > 0) {
            uint256 ta = vault.totalAssets();
            if (ta > 0) {
                feeSettings.navPerShareLastFeeMark = (ta * FEE_DIVISOR) / ts;
            } else {
                feeSettings.navPerShareLastFeeMark = FEE_DIVISOR;
            }
        }
        emit StreamingFeeSet(fee);
    }
LOW severity: [STAKE-6] Unprotected flash loan callback can be abused to manipulate/claim other users' positions. **Severity:** Critical

**Path:** LeverageStrategy.sol:receiveFlashLoan#L304-L333

**Description:** The Leverage Strategy contract uses flash loans from the osToken Flash Loan module to flash loan osTokens and perform actions, such as deposit, claim and rescue. Each of these functions performs the.....

Vulnerable code:
function receiveFlashLoan(uint256 osTokenShares, bytes memory userData) external {
        // validate sender
        if (msg.sender != address(_osTokenFlashLoans)) {
            revert Errors.AccessDenied();
        }

        // decode userData action
        (FlashloanAction flashloanType) = abi.decode(userData, (FlashloanAction));
        if (flashloanType == FlashloanAction.Deposit) {
            // process deposit flashloan
            (, address vault, address proxy) = abi.decode(userData, (FlashloanAction, address, address));
            _processDepositFlashloan(vault, proxy, osTokenShares);
        } else if (flashloanType == FlashloanAction.ClaimExitedAssets) {
            // process claim exited assets flashloan
            (, address vault, address proxy, uint256 exitPositionTicket) =
                abi.decode(userData, (FlashloanAction, address, address, uint256));
            _processClaimFlashloan(vault, proxy, exitPositionTicket, osTokenShares);
        } else if (flashloanType == FlashloanAction.RescueVaultAssets) {
            // process vault assets rescue flashloan
            (, address vault, address proxy, uint256 exitPositionTicket) =
                abi.decode(userData, (FlashloanAction, address, address, uint256));
            _processVaultAssetsRescueFlashloan(vault, proxy, exitPositionTicket, osTokenShares);
        } else if (flashloanType == FlashloanAction.RescueLendingAssets) {
            // process lending assets rescue flashloan
            (, address proxy, uint256 assets) = abi.decode(userData, (FlashloanAction, address, uint256));
            _processLendingAssetsRescueFlashloan(proxy, assets, osTokenShares);
        } else {
            revert InvalidFlashloanAction();
        }
    }

function deposit(address vault, uint256 osTokenShares) external {
  [..]
  if (performingFlashLoan) {
    revert Errors.Reentrant();
  }
  performingFlashLoan = true;
  // execute flashloan
  _osTokenFlashLoans.flashLoan(
    address(this), flashloanOsTokenShares, abi.encode(FlashloanAction.Deposit, vault, proxy)
  );
  performingFlashLoan = false;
  [..]
}

function receiveFlashLoan(uint256 osTokenShares, bytes memory userData) external {
  if (!performingFlashLoan) {
    revert Errors.AccessDenied();
  }
  [..]
}
LOW severity: [STAKE-7] Lack of slippage protection in deposit function. **Severity:** Medium

**Path:** src/leverage/LeverageStrategy.sol::deposit()#L125-L182

**Description:** The `deposit()` function in the `LeverageStrategy.sol` contract executes several key operations involving token transfers, asset borrowing, and minting, including the potential use of flash...

Vulnerable code:
function deposit(address vault, uint256 osTokenShares) external {
        if (osTokenShares == 0) revert Errors.InvalidShares();

        // fetch strategy proxy
        (address proxy,) = _getOrCreateStrategyProxy(vault, msg.sender);
        if (isStrategyProxyExiting[proxy]) revert Errors.ExitRequestNotProcessed();

        // transfer osToken shares from user to the proxy
        IStrategyProxy(proxy).execute(
            address(_osToken), abi.encodeWithSelector(_osToken.transferFrom.selector, msg.sender, proxy, osTokenShares)
        );

        // fetch vault state and lending protocol state
        (uint256 stakedAssets, uint256 mintedOsTokenShares) = _getVaultState(vault, proxy);
        (uint256 borrowedAssets, uint256 suppliedOsTokenShares) = _getBorrowState(proxy);

        // check whether any of the positions exist
        uint256 leverageOsTokenShares = osTokenShares;
        if (stakedAssets != 0 || mintedOsTokenShares != 0 || borrowedAssets != 0 || suppliedOsTokenShares != 0) {
            // supply osToken shares to the lending protocol
            _supplyOsTokenShares(proxy, osTokenShares);
            suppliedOsTokenShares += osTokenShares;

            // borrow max amount of assets from the lending protocol
            uint256 maxBorrowAssets =
                Math.mulDiv(_osTokenVaultController.convertToAssets(suppliedOsTokenShares), _getBorrowLtv(), _wad);
            if (borrowedAssets >= maxBorrowAssets) {
                // nothing to borrow
                emit Deposited(vault, msg.sender, osTokenShares, 0);
                return;
            }
            uint256 assetsToBorrow;
            unchecked {
                // cannot underflow because maxBorrowAssets > borrowedAssets
                assetsToBorrow = maxBorrowAssets - borrowedAssets;
            }
            _borrowAssets(proxy, assetsToBorrow);

            // mint max possible osToken shares
            leverageOsTokenShares = _mintOsTokenShares(vault, proxy, assetsToBorrow, type(uint256).max);
            if (leverageOsTokenShares == 0) {
                // no osToken shares to leverage
                emit Deposited(vault, msg.sender, osTokenShares, 0);
                return;
            }
        }

        // calculate flash loaned osToken shares
        uint256 flashloanOsTokenShares = _getFlashloanOsTokenShares(vault, leverageOsTokenShares);

        // execute flashloan
        _osTokenFlashLoans.flashLoan(
            address(this), flashloanOsTokenShares, abi.encode(FlashloanAction.Deposit, vault, proxy)
        );

        // emit event
        emit Deposited(vault, msg.sender, osTokenShares, flashloanOsTokenShares);
    }
HIGH severity: [STAKE-5] Permit function can be frontrunned. **Severity:** Low

**Path:** src/leverage/LeverageStrategy.sol::permit()#L114-L122

**Description:** The `permit()` function within the `LeverageStrategy.sol` contract is designed to allow token approvals through signatures, leveraging the ERC-2612 standard. However, the function is vulnerable to...

Vulnerable code:
function permit(address vault, uint256 osTokenShares, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
        (address proxy,) = _getOrCreateStrategyProxy(vault, msg.sender);
        IStrategyProxy(proxy).execute(
            address(_osToken),
            abi.encodeWithSelector(
                IERC20Permit(address(_osToken)).permit.selector, msg.sender, proxy, osTokenShares, deadline, v, r, s
            )
        );
    }
LOW severity: [STAKE-9] Incorrect rounding in mintShares and cumulativeFeePerShare calculations. **Severity:** Low

**Path:** lib/v3-core/contracts/tokens/OsTokenVaultController.sol::mintShares()#L103-L129

lib/v3-core/contracts/tokens/OsTokenVaultController.sol::cumulativeFeePerShare()#L200-L228

**Description:** In the `OsTokenVaultController.sol` contract, the functions `mintShares()` and...

Vulnerable code:
function convertToAssets(uint256 shares) public view override returns (uint256 assets) {
    return _convertToAssets(shares, _totalShares, totalAssets(), Math.Rounding.Floor);
  }

function mintShares(address receiver, uint256 shares) external override returns (uint256 assets) {
    if (
      !IVaultsRegistry(_registry).vaults(msg.sender) ||
      !IVaultsRegistry(_registry).vaultImpls(IVaultVersion(msg.sender).implementation())
    ) {
      revert Errors.AccessDenied();
    }
    if (receiver == address(0)) revert Errors.ZeroAddress();
    if (shares == 0) revert Errors.InvalidShares();

    // pull accumulated rewards
    updateState();

    // calculate amount of assets to mint
    assets = convertToAssets(shares);

    uint256 totalAssetsAfter = _totalAssets + assets;
    if (totalAssetsAfter > capacity) revert Errors.CapacityExceeded();

    // update counters
    _totalShares += SafeCast.toUint128(shares);
    _totalAssets = SafeCast.toUint128(totalAssetsAfter);

    // mint shares
    IOsToken(_osToken).mint(receiver, shares);
    emit Mint(msg.sender, receiver, assets, shares);
  }

function cumulativeFeePerShare() external view override returns (uint256) {
    // SLOAD to memory
    uint256 currCumulativeFeePerShare = _cumulativeFeePerShare;

    // calculate rewards
    uint256 profitAccrued = _unclaimedAssets();
    if (profitAccrued == 0) return currCumulativeFeePerShare;

    // calculate treasury assets
    uint256 treasuryAssets = Math.mulDiv(profitAccrued, feePercent, _maxFeePercent);
    if (treasuryAssets == 0) return currCumulativeFeePerShare;

    // SLOAD to memory
    uint256 totalShares_ = _totalShares;

    // calculate treasury shares
    uint256 treasuryShares;
    unchecked {
      treasuryShares = _convertToShares(
        treasuryAssets,
        totalShares_,
        // cannot underflow because profitAccrued >= treasuryAssets
        _totalAssets + profitAccrued - treasuryAssets,
        Math.Rounding.Floor
      );
    }

    return currCumulativeFeePerShare + Math.mulDiv(treasuryShares, _wad, totalShares_);
  }
HIGH severity: [LOGLAB-13] The strategy does not pause when the deviation of sizeDeltaInTokens exceeds the threshold. **Severity:** High

**Description:** The function `BasisStrategy._afterDecreasePosition()` is called after the hedge position is decreased. This function returns a boolean value, `shouldPause`, to indicate whether the strategy should pause.  

To determine the value of `shouldPause`, the function...

Vulnerable code:
if (requestParams.collateralDeltaAmount > 0) {
        (bool exceedsThreshold,) = _checkDeviation(
            responseParams.collateralDeltaAmount, requestParams.collateralDeltaAmount, _responseDeviationThreshold
        );
        shouldPause = exceedsThreshold;
    }
LOW severity: [LOGLAB-10] Unable to redeem all user shares or sell all products and close the hedge when the decreaseSizeMax config is different from type(uint256).max. **Severity:** High

**Path:** src/strategy/BasisStrategy.sol#L577-L584, src/strategy/BasisStrategy.sol#L743-L758 

**Description:** To process the withdrawal of assets for users, the operator needs to call the `BasisStrategy::deutilize()` function to sell the spot product and send a decrease...

Vulnerable code:
function spotSellCallback(uint256 assetDelta, uint256 productDelta) external authCaller(spotManager()) {
    [...]
    if (!processingRebalanceDown()) {
        if ($.vault.totalSupply() == 0 || ISpotManager(_msgSender()).exposure() == 0) {
            // in case of redeeming all by users,
            // or selling out all product
            // close hedge position
            sizeDeltaInTokens = type(uint256).max;
            collateralDeltaAmount = type(uint256).max;
            $.pendingDecreaseCollateral = 0;
        }

function _afterDecreasePosition(IHedgeManager.AdjustPositionPayload calldata responseParams)
    private
    returns (bool shouldPause)
{
    BasisStrategyStorage storage $ = _getBasisStrategyStorage();
    IHedgeManager.AdjustPositionPayload memory requestParams = $.requestParams;
    if (requestParams.sizeDeltaInTokens == type(uint256).max) {
        // when closing hedge
        requestParams.sizeDeltaInTokens = responseParams.sizeDeltaInTokens;
        requestParams.collateralDeltaAmount = responseParams.collateralDeltaAmount;
    }
    [...]
}

function _adjustPosition(uint256 sizeDeltaInTokens, uint256 collateralDeltaAmount, bool isIncrease)
    [...]
    if (sizeDeltaInTokens > 0) {
        uint256 min;
        uint256 max;
        if (isIncrease) (min, max) = $.hedgeManager.increaseSizeMinMax();
        else (min, max) = $.hedgeManager.decreaseSizeMinMax();

        sizeDeltaInTokens = _clamp(min, sizeDeltaInTokens, max);
    }
LOW severity: [LOGLAB-17] pendingDecreaseCollateral variable isn't excluded from the positionNetBalance() value in the leverage and rebalance calculations, which may lead to incorrect rebalance actions for the strategy. **Severity:** High

**Path:** src/strategy/BasisStrategy.sol#L779-L884

**Description:** During a partial deutilizing action, the `BasisStrategy::spotSellCallback()` function will assign the `collateralDeltaToDecrease` value to the `$.pendingDecreaseCollateral` storage variable when...

Vulnerable code:
collateralDeltaToDecrease += _pendingDecreaseCollateral;
uint256 limitDecreaseCollateral = _hedgeManager.limitDecreaseCollateral();
if (collateralDeltaToDecrease < limitDecreaseCollateral) {
    $.pendingDecreaseCollateral = collateralDeltaToDecrease;
} else {
    collateralDeltaAmount = collateralDeltaToDecrease;
}

function spotSellCallback(uint256 assetDelta, uint256 productDelta) external authCaller(spotManager()) {
    [...]
    // when partial deutilizing
    IHedgeManager _hedgeManager = $.hedgeManager;
    uint256 positionNetBalance = _hedgeManager.positionNetBalance();
    uint256 _pendingDecreaseCollateral = $.pendingDecreaseCollateral;
    if (_pendingDecreaseCollateral > 0) {
        (, positionNetBalance) = positionNetBalance.trySub(_pendingDecreaseCollateral);
    }
    uint256 positionSizeInTokens = _hedgeManager.positionSizeInTokens();
    uint256 collateralDeltaToDecrease =
        positionNetBalance.mulDiv(productDelta, positionSizeInTokens);

function _pendingDeutilization(InternalPendingDeutilization memory params) private view returns (uint256) {
    [...]
    deutilization = positionSizeInTokens.mulDiv(
        totalPendingWithdraw - _pendingDecreaseCollateral,
        positionSizeInAssets + positionNetBalance - _pendingDecreaseCollateral
    );
LOW severity: [LOGLAB-15] Last withdrawer could give next depositor 0 shares. **Severity:** High

**Path:** src/vault/LogarithmVault.sol#L510-L513

**Description:** The `claim` function has a different workflow for the last redeem, this requires `totalSupply` = 0 and `utilizedAssets` = 0 (meaning the last withdraw was executed).

To be able to claim, a user must first make...

Vulnerable code:
function claim(bytes32 withdrawRequestKey) public virtual returns (uint256) {
    -- Snip -- 
        if (isLast) {
            -- Snip -- 
            if (shortfall > 0) {
               -- Snip -- 
            } else {
                uint256 _idleAssets = idleAssets(); <= transfer transfered funds back to user       
                executedAssets = withdrawRequest.requestedAssets + _idleAssets;
                $.assetsToClaim += _idleAssets;
            }
        } else {
            executedAssets = withdrawRequest.requestedAssets;
        }
        
        $.assetsToClaim -= executedAssets;
        IERC20(asset()).safeTransfer(withdrawRequest.receiver, executedAssets);
        emit Claimed(withdrawRequest.receiver, withdrawRequestKey, executedAssets);
        return executedAssets;
    }
LOW severity: [LOGLAB-22] Lack of slippage protection for manual swap in SpotManager. **Severity:** Medium

**Path:** src/spot/SpotManager.sol#L145-L188, src/libraries/uniswap/ManualSwapLogic.sol#L16-L30

**Description:** In the `SpotManager` contract, the `buy()` and `sell()` functions attempt to swap tokens between assets and products via the 1inch router or through manual swaps...

Vulnerable code:
amountOut = ManualSwapLogic.swap(amount, $.productToAssetSwapPath);

function buy(uint256 amount, SwapType swapType, bytes calldata swapData) external authCaller(strategy()) {
    uint256 amountOut;
    if (swapType == SwapType.INCH_V6) {
        bool success;
        (amountOut, success) = InchAggregatorV6Logic.executeSwap(amount, asset(), product(), true, swapData);
        if (!success) {
            revert Errors.SwapFailed();
        }
    } else if (swapType == SwapType.MANUAL) {
        SpotManagerStorage storage $ = _getSpotManagerStorage();
        amountOut = ManualSwapLogic.swap(amount, $.assetToProductSwapPath);
    } else {
        // TODO: fallback swap
        revert Errors.UnsupportedSwapType();
    }
    emit SpotBuy(amount, amountOut);

    IBasisStrategy(_msgSender()).spotBuyCallback(amount, amountOut);
}

function sell(uint256 amount, SwapType swapType, bytes calldata swapData) external authCaller(strategy()) {
    uint256 amountOut;
    if (swapType == SwapType.INCH_V6) {
        bool success;
        (amountOut, success) = InchAggregatorV6Logic.executeSwap(amount, asset(), product(), false, swapData);
        if (!success) {
            revert Errors.SwapFailed();
        }
    } else if (swapType == SwapType.MANUAL) {
        SpotManagerStorage storage $ = _getSpotManagerStorage();
        amountOut = ManualSwapLogic.swap(amount, $.productToAssetSwapPath);
    } else {
        // TODO: fallback swap
        revert Errors.UnsupportedSwapType();
    }
    emit SpotSell(amountOut, amount);

    IBasisStrategy(_msgSender()).spotSellCallback(amountOut, amount);
}

function swap(uint256 amountIn, address[] memory path) external returns (uint256 amountOut) {
    address tokenIn = path[0];
    uint256 balance = IERC20(tokenIn).balanceOf(address(this));
    if (balance < amountIn) {
        revert Errors.SwapAmountExceedsBalance(amountIn, balance);
    }

    for (uint256 i; i <= path.length / 2; i += 2) {
        address pool = path[i + 1];
        amountIn = exactInputInternal(
            amountIn, address(this), pool, path[i] < path[i + 2], abi.encode(path[i], path[i + 2], address(this))
        );
    }
    amountOut = amountIn;
}

function exactInputInternal(uint256 amountIn, address recipient, address pool, bool zeroForOne, bytes memory data)
    internal
    returns (uint256 amountOut)
{
    (int256 amount0, int256 amount1) = IUniswapV3Pool(pool).swap(
        recipient,
        zeroForOne,
        amountIn.toInt256(),
        zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
        data
    );
    amountOut = uint256(-(zeroForOne ? amount1 : amount0));
}
LOW severity: [LOGLAB-22] Not all pendingDecreaseCollateral is utilized due to the max limit. **Severity:** Medium

**Path:** src/strategy/BasisStrategy.sol#L601-L607, src/strategy/BasisStrategy.sol#L752-L758

**Description:** In scenarios where the strategy is partially deutilizing, the storage variable `pendingDecreaseCollateral` is added to `collateralDeltaToDecrease` on line 601 of the.....

Vulnerable code:
collateralDeltaToDecrease += _pendingDecreaseCollateral;
uint256 limitDecreaseCollateral = _hedgeManager.limitDecreaseCollateral();
if (collateralDeltaToDecrease < limitDecreaseCollateral) {
    $.pendingDecreaseCollateral = collateralDeltaToDecrease;
} else {
    collateralDeltaAmount = collateralDeltaToDecrease;
}

if (collateralDeltaAmount > 0) {
    uint256 min;
    uint256 max;
    if (isIncrease) (min, max) = $.hedgeManager.increaseCollateralMinMax();
    else (min, max) = $.hedgeManager.decreaseCollateralMinMax();
    collateralDeltaAmount = _clamp(min, collateralDeltaAmount, max);
}
LOW severity: [LOGLAB-19] Unable to execute the final withdrawal due to utilizedAssets() not being zero. **Severity:** Medium

**Path:** src/vault/LogarithmVault.sol#L758-L761 

**Description:** The function `LogarithmVault._isWithdrawRequestExecuted()` determines whether a specific withdrawal request can be executed. It returns a boolean variable, `isExecuted`, to indicate if the user’s request has...

Vulnerable code:
function utilizedAssets() public view returns (uint256) {
    BasisStrategyStorage storage $ = _getBasisStrategyStorage();
    return 
        $.spotManager.getAssetValue() + 
        $.hedgeManager.positionNetBalance() + 
        assetsToWithdraw(); 
}

function assetsToWithdraw() public view returns (uint256) {
    return IERC20(asset()).balanceOf(address(this));
}

function positionNetBalance() public view virtual override returns (uint256) {
    OffChainPositionManagerStorage storage $ = _getOffChainPositionManagerStorage();
    PositionState memory state = $.positionStates[$.currentRound];
        
    uint256 initialNetBalance = state.netBalance 
                              + $.pendingCollateralIncrease 
                              + idleCollateralAmount(); 
    ... 
}

function idleCollateralAmount() public view returns (uint256) {
    return IERC20(collateralToken()).balanceOf(address(this));
}

if (isLast) {
    // last withdraw is claimable when utilized assets is 0
    isExecuted = IStrategy(strategy()).utilizedAssets() == 0;
} else {
LOW severity: [LOGLAB-14] Redundant and ineffective staleness check implementation. **Severity:** Low

**Path:** src/oracle/LogarithmOracle.sol#L110-115

**Description:** The `LogarithmOracle` uses Chainlink data feeds to fetch assets prices. This oracle exposes the `latestRoundData` function to return pricing data.

There is a staleness check, the first part of which is...

Vulnerable code:
uint256 heartbeatDuration = _getLogarithmOracleStorage().heartbeatDurations[address(priceFeed)];
if (block.timestamp > timestamp && block.timestamp - timestamp > heartbeatDuration) {
    revert Errors.PriceFeedNotUpdated(asset, timestamp, heartbeatDuration);
}
LOW severity: [LOGLAB-9] The change in priority after requestWithdraw may block the claiming of the withdrawal request. **Severity:** Low

**Path:** src/vault/LogarithmVault.sol#L408-L419, src/vault/LogarithmVault.sol#L762-L764

**Description:** If the priority is changed after a withdraw request is made, the withdrawal might not be able to be claimed.
The issue is that the withdrawRequest saves the...

Vulnerable code:
if (isPrioritized(owner)) {
    _accRequestedWithdrawAssets = $.prioritizedAccRequestedWithdrawAssets + assetsToRequest;
    $.prioritizedAccRequestedWithdrawAssets = _accRequestedWithdrawAssets;
} else {
    _accRequestedWithdrawAssets = $.accRequestedWithdrawAssets + assetsToRequest;
    $.accRequestedWithdrawAssets = _accRequestedWithdrawAssets;
}


bytes32 withdrawKey = getWithdrawKey(owner, _useNonce(owner));
$.withdrawRequests[withdrawKey] = WithdrawRequest({
    requestedAssets: assetsToRequest,
    accRequestedWithdrawAssets: _accRequestedWithdrawAssets,

isExecuted = isPrioritizedAccount
    ? accRequestedWithdrawAssetsOfRequest <= $.prioritizedProcessedWithdrawAssets
    : accRequestedWithdrawAssetsOfRequest <= $.processedWithdrawAssets;
LOW severity: [LOGLAB-11] `LogarithmVault.sol::maxMint` is returning `super.maxDeposit` instead of `super.maxMint`. **Severity:** low

**Path:** src/vault/LogarithmVault.sol#L652 

**Description:** The `LogarithmVault::maxMint` function at line 652 incorrectly calls `super.maxDeposit()`. Instead, it should convert the user's maximum deposit into shares by calling `super.maxMint()` as...

Vulnerable code:
function maxMint(address receiver) public view virtual override returns (uint256) {
    if (paused() || isShutdown()) {
        return 0;
    } else {
        return super.maxDeposit(receiver);
    }
}