Skip to content

Latest commit

 

History

History
127 lines (106 loc) · 6.66 KB

File metadata and controls

127 lines (106 loc) · 6.66 KB

Lone Wintergreen Rattlesnake

Medium

Multiple Fraxlend pairs can use the same available assets from vault when updating interest leading to incorrect calculations

Summary

The logic in the totalAvailableAssetsForVault is quite clear, but there seems to be an issue when about 10 out of 20 lending pairs are updating interest without borrowing, when 10 out of 20 whitelisted pairs are self-dependent, each of them has 1_000_000 as vaultMaxAllocation and 10 of 20 of them already used 5_000 as vaultDeposits, when the _overallAvailable is 1,000,000, when there is an update from the leverage manager i.e removing leverage, it updates the interest, or any function that depends on this function from the FraxlendPair contracts, that _updateInterestAndMdInAllVaults, or _previewAddInterestAndMdInAllVaults, each of them tries to update their interest without borrowing, 10 of 20 uses the _totalVaultAvailable which is 1,000,000 - 5,000, and the remaining 10 uses 1,000,000 without using the assets, and they all use this amount to update their interest. this could be an issue as each of these vaults supposed to have an allocation Ratio that can be use from the overall available so it reduces the interest rates of lenders.

           uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));

            // Get the utilization rate
            uint256 _utilizationRate =
                _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;

            // Request new interest rate and full utilization rate from the rate calculator
            (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
                _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
            );

Root Cause

The issue stems from how totalAvailableAssetsForVault calculates available assets without considering proper allocation ratios when multiple lending pairs update their interest rates. Here's the detailed breakdown:

Initial State:

- 20 total whitelisted lending pairs
- 10 pairs are self-dependent
- Each pair has vaultMaxAllocation = 1,000,000
- 10 pairs have used vaultDeposits = 5,000
- _overallAvailable = 1,000,000

The Problem in totalAvailableAssetsForVault:

function totalAvailableAssetsForVault(address _vault) public view override returns (uint256 _totalVaultAvailable) {
    uint256 _overallAvailable = totalAvailableAssets();

    _totalVaultAvailable =
        vaultMaxAllocation[_vault] > vaultDeposits[_vault] ? vaultMaxAllocation[_vault] - vaultDeposits[_vault] : 0;

    _totalVaultAvailable = _overallAvailable < _totalVaultAvailable ? _overallAvailable : _totalVaultAvailable;
}

When interest updates are triggered for instance: 10 pairs correctly calculate available assets as: 1,000,000 - 5,000 = 995,000 Other 10 pairs use the full 1,000,000 without considering actual usage Impact on Interest Rate Calculation:

            uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(address(externalAssetVault));

            // Get the utilization rate
            uint256 _utilizationRate =
                _totalAssetsAvailable == 0 ? 0 : (UTIL_PREC * _results.totalBorrow.amount) / _totalAssetsAvailable;

            // Request new interest rate and full utilization rate from the rate calculator
            (_results.newRate, _results.newFullUtilizationRate) = IRateCalculatorV2(rateContract).getNewRate(
                _deltaTime, _utilizationRate, _currentRateInfo.fullUtilizationRate
            );

The utilization rate calculation becomes inaccurate because _totalAssetsAvailable isn't properly scaled by allocation ratios This leads to incorrect interest rates being calculated for lenders Each vault calculates interest independently without considering its proper share of the total available assets.

Impact

Incorrect Interest Rate Calculation

  • Some vaults calculate their available assets based on Max Allocation rather than actual deposits.
  • This results in a misleadingly low utilization rate, causing lower-than-expected interest rates for lenders.

Overstatement of Available Assets

  • Certain vaults assume they have access to all available assets, even though only a fraction is actually utilized.
  • This affects 10 out of 20 self-dependent vaults (Vaults that doesnt require loan from the MetaVault, but whitelisted on the Metavault), which continue updating their interest rates based on an inflated asset pool.

Systemic Yield Suppression for Lenders

  • Since interest rates depend on utilization, underestimating utilization leads to lower yields for lenders.
  • This issue scales across multiple vaults, causing protocol-wide mispricing of borrowing/lending rates.

Mitigation

Add a check for self-sustaining vaults (Vaults that doesnt require loan from the MetaVault, but whitelisted on the Metavault)

function isSelfSustainable() public view returns (bool) {
        return IERC4626Extended(vault).vaultDeposits() > 0;
    }

This should be use anywhere interest is been updated to determine if allocation from lending vault is required.

    function addInterest(bool _returnAccounting)
        external
        nonReentrant
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _currentRateInfo,
            VaultAccount memory _totalAsset,
            VaultAccount memory _totalBorrow
        )
    {
        // ...
        address _IsVaultSelfSustainable = isSelfSustainable() == true ? address(externalAssetVault) : address(0); 
        uint256 _totalAssetsAvailable = totalAsset.totalAmount(_IsVaultSelfSustainable);
        // ...
    }
function _calculateInterest(CurrentRateInfo memory _currentRateInfo)
        internal
        view
        returns (InterestCalculationResults memory _results)
    {
        // Short circuit if interest already calculated this block OR if interest is paused
        if (_currentRateInfo.lastTimestamp != block.timestamp && !isInterestPaused) {
            // ...

            // Total assets available including what resides in the external vault
            address _IsVaultSelfSustainable = isSelfSustainable() == true ? address(externalAssetVault) : address(0); 
            uint256 _totalAssetsAvailable = totalAsset.totalAmount(_IsVaultSelfSustainable);

            uint256 _totalAssetsAvailable = _results.totalAsset.totalAmount(_totalAssetsAvailable);
            // ...
        }
    }