HIGH severity: Inconsistent balance tracking in vault creates DoS for asset borrowing. The Vault contract uses inconsistent accounting methods: some functions rely on totalSupplies while others use direct balanceOf checks. The burn function reduces totalSupplies without validating against actual token balances, allowing totalSupplies to drop below totalBorrows. This violates the invar...

Vulnerable code:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import { IVault } from "../../interfaces/IVault.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @title Vault for storing the backing for cTokens
/// @author kexley, @capLabs
/// @notice Tokens are supplied by cToken minters and borrowed by covered agents
/// @dev Supplies, borrows and utilization rates are tracked. Interest rates should be computed and
/// charged on the external contracts, only the principle amount is counted on this contract.
library VaultLogic {
    using SafeERC20 for IERC20;

    /// @dev Timestamp is past the deadline
    error PastDeadline();

    /// @dev Amount out is less than required
    error Slippage(address asset, uint256 amountOut, uint256 minAmountOut);

    /// @dev Paused assets cannot be supplied or borrowed
    error AssetPaused(address asset);

    /// @dev Only whitelisted assets can be supplied or borrowed
    error AssetNotSupported(address asset);

    /// @dev Asset is already listed
    error AssetAlreadySupported(address asset);

    /// @dev Only non-supported assets can be rescued
    error AssetNotRescuable(address asset);

    /// @dev Invalid min amounts out as they dont match the number of assets
    error InvalidMinAmountsOut();

    /// @dev Insufficient reserves
    error InsufficientReserves(address asset, uint256 balanceBefore, uint256 amount);

    /// @dev Cap token minted
    event Mint(address indexed minter, address receiver, address indexed asset, uint256 amountIn, uint256 amountOut);

    /// @dev Cap token burned
    event Burn(address indexed burner, address receiver, address indexed asset, uint256 amountIn, uint256 amountOut);

    /// @dev Cap token redeemed
    event Redeem(address indexed redeemer, address receiver, uint256 amountIn, uint256[] amountsOut);

    /// @dev Borrow made
    event Borrow(address indexed borrower, address indexed asset, uint256 amount);

    /// @dev Repayment made
    event Repay(address indexed repayer, address indexed asset, uint256 amount);

    /// @dev Add asset
    event AddAsset(address asset);

    /// @dev Remove asset
    event RemoveAsset(address asset);

    /// @dev Asset paused
    event PauseAsset(address asset);

    /// @dev Asset unpaused
    event UnpauseAsset(address asset);

    /// @dev Rescue unsupported ERC20 tokens
    event RescueERC20(address asset, address receiver);

    /// @dev Modifier to only allow supplies and borrows when not paused
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    modifier whenNotPaused(IVault.VaultStorage storage $, address _asset) {
        _whenNotPaused($, _asset);
        _;
    }

    /// @dev Modifier to update the utilization index
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    modifier updateIndex(IVault.VaultStorage storage $, address _asset) {
        _updateIndex($, _asset);
        _;
    }

    /// @notice Mint the cap token using an asset
    /// @dev This contract must have approval to move asset from msg.sender
    /// @param $ Vault storage pointer
    /// @param params Mint parameters
    function mint(IVault.VaultStorage storage $, IVault.MintBurnParams memory params)
        external
        whenNotPaused($, params.asset)
        updateIndex($, params.asset)
    {
        if (params.deadline < block.timestamp) revert PastDeadline();
        if (params.amountOut < params.minAmountOut) {
            revert Slippage(address(this), params.amountOut, params.minAmountOut);
        }

        $.totalSupplies[params.asset] += params.amountIn;

        IERC20(params.asset).safeTransferFrom(msg.sender, address(this), params.amountIn);

        emit Mint(msg.sender, params.receiver, params.asset, params.amountIn, params.amountOut);
    }

    /// @notice Burn the cap token for an asset
    /// @dev Can only withdraw up to the amount remaining on this contract
    /// @param $ Vault storage pointer
    /// @param params Burn parameters
    function burn(IVault.VaultStorage storage $, IVault.MintBurnParams memory params)
        external
        updateIndex($, params.asset)
    {
        if (params.deadline < block.timestamp) revert PastDeadline();
        if (params.amountOut < params.minAmountOut) {
            revert Slippage(params.asset, params.amountOut, params.minAmountOut);
        }

        $.totalSupplies[params.asset] -= params.amountOut;

        IERC20(params.asset).safeTransfer(params.receiver, params.amountOut);

        emit Burn(msg.sender, params.receiver, params.asset, params.amountIn, params.amountOut);
    }

    /// @notice Redeem the Cap token for a bundle of assets
    /// @dev Can only withdraw up to the amount remaining on this contract
    /// @param $ Vault storage pointer
    /// @param params Redeem parameters
    function redeem(IVault.VaultStorage storage $, IVault.RedeemParams memory params) external {
        if (params.amountsOut.length != $.assets.length) revert InvalidMinAmountsOut();
        if (params.deadline < block.timestamp) revert PastDeadline();

        address[] memory cachedAssets = $.assets;
        for (uint256 i; i < cachedAssets.length; ++i) {
            if (params.amountsOut[i] < params.minAmountsOut[i]) {
                revert Slippage(cachedAssets[i], params.amountsOut[i], params.minAmountsOut[i]);
            }
            _updateIndex($, cachedAssets[i]);
            $.totalSupplies[cachedAssets[i]] -= params.amountsOut[i];
            IERC20(cachedAssets[i]).safeTransfer(params.receiver, params.amountsOut[i]);
        }

        emit Redeem(msg.sender, params.receiver, params.amountIn, params.amountsOut);
    }

    /// @notice Borrow an asset
    /// @dev Whitelisted agents can borrow any amount, LTV is handled by Agent contracts
    /// @param $ Vault storage pointer
    /// @param params Borrow parameters
    function borrow(IVault.VaultStorage storage $, IVault.BorrowParams memory params)
        external
        whenNotPaused($, params.asset)
        updateIndex($, params.asset)
    {
        uint256 balanceBefore = IERC20(params.asset).balanceOf(address(this));
        if (balanceBefore < params.amount) revert InsufficientReserves(params.asset, balanceBefore, params.amount);

        $.totalBorrows[params.asset] += params.amount;
        IERC20(params.asset).safeTransfer(params.receiver, params.amount);

        emit Borrow(msg.sender, params.asset, params.amount);
    }

    /// @notice Repay an asset
    /// @param $ Vault storage pointer
    /// @param params Repay parameters
    function repay(IVault.VaultStorage storage $, IVault.RepayParams memory params)
        external
        updateIndex($, params.asset)
    {
        $.totalBorrows[params.asset] -= params.amount;
        IERC20(params.asset).safeTransferFrom(msg.sender, address(this), params.amount);

        emit Repay(msg.sender, params.asset, params.amount);
    }

    /// @notice Add an asset to the vault list
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    function addAsset(IVault.VaultStorage storage $, address _asset) external {
        if (_listed($, _asset)) revert AssetAlreadySupported(_asset);

        $.assets.push(_asset);
        emit AddAsset(_asset);
    }

    /// @notice Remove an asset from the vault list
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    function removeAsset(IVault.VaultStorage storage $, address _asset) external {
        address[] memory cachedAssets = $.assets;
        uint256 length = cachedAssets.length;
        bool removed;
        for (uint256 i; i < length; ++i) {
            if (_asset == cachedAssets[i]) {
                $.assets[i] = cachedAssets[length - 1];
                $.assets.pop();
                removed = true;
                break;
            }
        }

        if (!removed) revert AssetNotSupported(_asset);

        emit RemoveAsset(_asset);
    }

    /// @notice Pause an asset
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    function pause(IVault.VaultStorage storage $, address _asset) external {
        $.paused[_asset] = true;
        emit PauseAsset(_asset);
    }

    /// @notice Unpause an asset
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    function unpause(IVault.VaultStorage storage $, address _asset) external {
        $.paused[_asset] = false;
        emit UnpauseAsset(_asset);
    }

    /// @notice Rescue an unsupported asset
    /// @param $ Vault storage pointer
    /// @param _asset Asset to rescue
    /// @param _receiver Receiver of the rescue
    function rescueERC20(IVault.VaultStorage storage $, address _asset, address _receiver) external {
        if (_listed($, _asset)) revert AssetNotRescuable(_asset);
        IERC20(_asset).safeTransfer(_receiver, IERC20(_asset).balanceOf(address(this)));
        emit RescueERC20(_asset, _receiver);
    }

    /// @notice Calculate the utilization ratio of an asset
    /// @dev Returns the ratio of borrowed assets to total supply, scaled to ray (1e27)
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    /// @return ratio Utilization ratio in ray (1e27)
    function utilization(IVault.VaultStorage storage $, address _asset) public view returns (uint256 ratio) {
        ratio = $.totalSupplies[_asset] != 0 ? $.totalBorrows[_asset] * 1e27 / $.totalSupplies[_asset] : 0;
    }

    /// @notice Up to date cumulative utilization index of an asset
    /// @dev Utilization and index are both scaled in ray (1e27)
    /// @param $ Vault storage pointer
    /// @param _asset Utilized asset
    /// @return index Utilization ratio index in ray (1e27)
    function currentUtilizationIndex(IVault.VaultStorage storage $, address _asset)
        external
        view
        returns (uint256 index)
    {
        index = $.utilizationIndex[_asset] + (utilization($, _asset) * (block.timestamp - $.lastUpdate[_asset]));
    }

    /// @notice Validate that an asset is listed
    /// @param $ Vault storage pointer
    /// @param _asset Asset to check
    /// @return isListed Asset is listed or not
    function _listed(IVault.VaultStorage storage $, address _asset) internal view returns (bool isListed) {
        address[] memory cachedAssets = $.assets;
        uint256 length = cachedAssets.length;
        for (uint256 i; i < length; ++i) {
            if (_asset == cachedAssets[i]) {
                isListed = true;
                break;
            }
        }
    }

    /// @dev Only allow supplies and borrows when not paused
    /// @param $ Vault storage pointer
    /// @param _asset Asset address
    function _whenNotPaused(IVault.VaultStorage storage $, address _asset) private view {
        if ($.paused[_asset]) revert AssetPaused(_asset);
    }

    /// @dev Update the cumulative utilization index of an asset
    /// @param $ Vault storage pointer
    /// @param _asset Utilized asset
    function _updateIndex(IVault.VaultStorage storage $, address _asset) internal {
        if (!_listed($, _asset)) revert AssetNotSupported(_asset);
        $.utilizationIndex[_asset] += utilization($, _asset) * (block.timestamp - $.lastUpdate[_asset]);
        $.lastUpdate[_asset] = block.timestamp;
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import { Access } from "../access/Access.sol";

import { IVault } from "../interfaces/IVault.sol";
import { VaultStorageUtils } from "../storage/VaultStorageUtils.sol";
import { FractionalReserve } from "./FractionalReserve.sol";
import { Minter } from "./Minter.sol";
import { VaultLogic } from "./libraries/VaultLogic.sol";
import { ERC20PermitUpgradeable } from
    "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";

/// @title Vault for storing the backing for cTokens
/// @author kexley, @capLabs
/// @notice Tokens are supplied by cToken minters and borrowed by covered agents
/// @dev Supplies, borrows and utilization rates are tracked. Interest rates should be computed and
/// charged on the external contracts, only the principle amount is counted on this contract.
contract Vault is IVault, ERC20PermitUpgradeable, Access, Minter, FractionalReserve, VaultStorageUtils {
    /// @dev Initialize the assets
    /// @param _name Name of the cap token
    /// @param _symbol Symbol of the cap token
    /// @param _accessControl Access control address
    /// @param _feeAuction Fee auction address
    /// @param _oracle Oracle address
    /// @param _assets Asset addresses
    function __Vault_init(
        string memory _name,
        string memory _symbol,
        address _accessControl,
        address _feeAuction,
        address _oracle,
        address[] calldata _assets
    ) internal onlyInitializing {
        __ERC20_init(_name, _symbol);
        __ERC20Permit_init(_name);
        __Access_init(_accessControl);
        __FractionalReserve_init_unchained(_feeAuction);
        __Minter_init_unchained(_oracle);
        __Vault_init_unchained(_assets);
    }

    /// @dev Initialize unchained
    /// @param _assets Asset addresses
    function __Vault_init_unchained(address[] calldata _assets) internal onlyInitializing {
        getVaultStorage().assets = _assets;
    }

    /// @notice Mint the cap token using an asset
    /// @dev This contract must have approval to move asset from msg.sender
    /// @param _asset Whitelisted asset to deposit
    /// @param _amountIn Amount of asset to use in the minting
    /// @param _minAmountOut Minimum amount to mint
    /// @param _receiver Receiver of the minting
    /// @param _deadline Deadline of the tx
    function mint(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
        external
        returns (uint256 amountOut)
    {
        amountOut = getMintAmount(_asset, _amountIn);
        VaultLogic.mint(
            getVaultStorage(),
            MintBurnParams({
                asset: _asset,
                amountIn: _amountIn,
                amountOut: amountOut,
                minAmountOut: _minAmountOut,
                receiver: _receiver,
                deadline: _deadline
            })
        );
        _mint(_receiver, amountOut);
    }

    /// @notice Burn the cap token for an asset
    /// @dev Asset is withdrawn from the reserve or divested from the underlying vault
    /// @param _asset Asset to withdraw
    /// @param _amountIn Amount of cap token to burn
    /// @param _minAmountOut Minimum amount out to receive
    /// @param _receiver Receiver of the withdrawal
    /// @param _deadline Deadline of the tx
    function burn(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
        external
        returns (uint256 amountOut)
    {
        amountOut = getBurnAmount(_asset, _amountIn);
        divest(_asset, amountOut);
        VaultLogic.burn(
            getVaultStorage(),
            MintBurnParams({
                asset: _asset,
                amountIn: _amountIn,
                amountOut: amountOut,
                minAmountOut: _minAmountOut,
                receiver: _receiver,
                deadline: _deadline
            })
        );
        _burn(msg.sender, _amountIn);
    }

    /// @notice Redeem the Cap token for a bundle of assets
    /// @dev Assets are withdrawn from the reserve or divested from the underlying vault
    /// @param _amountIn Amount of Cap token to burn
    /// @param _minAmountsOut Minimum amounts of assets to withdraw
    /// @param _receiver Receiver of the withdrawal
    /// @param _deadline Deadline of the tx
    /// @return amountsOut Amount of assets withdrawn
    function redeem(uint256 _amountIn, uint256[] calldata _minAmountsOut, address _receiver, uint256 _deadline)
        external
        returns (uint256[] memory amountsOut)
    {
        amountsOut = getRedeemAmount(_amountIn);
        divestMany(assets(), amountsOut);
        VaultLogic.redeem(
            getVaultStorage(),
            RedeemParams({
                amountIn: _amountIn,
                amountsOut: amountsOut,
                minAmountsOut: _minAmountsOut,
                receiver: _receiver,
                deadline: _deadline
            })
        );
        _burn(msg.sender, _amountIn);
    }

    /// @notice Borrow an asset
    /// @dev Whitelisted agents can borrow any amount, LTV is handled by Agent contracts
    /// @param _asset Asset to borrow
    /// @param _amount Amount of asset to borrow
    /// @param _receiver Receiver of the borrow
    function borrow(address _asset, uint256 _amount, address _receiver) external checkAccess(this.borrow.selector) {
        divest(_asset, _amount);
        VaultLogic.borrow(getVaultStorage(), BorrowParams({ asset: _asset, amount: _amount, receiver: _receiver }));
    }

    /// @notice Repay an asset
    /// @param _asset Asset to repay
    /// @param _amount Amount of asset to repay
    function repay(address _asset, uint256 _amount) external checkAccess(this.repay.selector) {
        VaultLogic.repay(getVaultStorage(), RepayParams({ asset: _asset, amount: _amount }));
    }

    /// @notice Add an asset to the vault list
    /// @param _asset Asset address
    function addAsset(address _asset) external checkAccess(this.addAsset.selector) {
        VaultLogic.addAsset(getVaultStorage(), _asset);
    }

    /// @notice Remove an asset from the vault list
    /// @param _asset Asset address
    function removeAsset(address _asset) external checkAccess(this.removeAsset.selector) {
        VaultLogic.removeAsset(getVaultStorage(), _asset);
    }

    /// @notice Pause an asset
    /// @param _asset Asset address
    function pause(address _asset) external checkAccess(this.pause.selector) {
        VaultLogic.pause(getVaultStorage(), _asset);
    }

    /// @notice Unpause an asset
    /// @param _asset Asset address
    function unpause(address _asset) external checkAccess(this.unpause.selector) {
        VaultLogic.unpause(getVaultStorage(), _asset);
    }

    /// @notice Rescue an unsupported asset
    /// @param _asset Asset to rescue
    /// @param _receiver Receiver of the rescue
    function rescueERC20(address _asset, address _receiver) external checkAccess(this.rescueERC20.selector) {
        VaultLogic.rescueERC20(getVaultStorage(), _asset, _receiver);
    }

    /// @notice Get the list of assets supported by the vault
    /// @return assetList List of assets
    function assets() public view returns (address[] memory assetList) {
        assetList = getVaultStorage().assets;
    }

    /// @notice Get the total supplies of an asset
    /// @param _asset Asset address
    /// @return totalSupply Total supply
    function totalSupplies(address _asset) external view returns (uint256 totalSupply) {
        totalSupply = getVaultStorage().totalSupplies[_asset];
    }

    /// @notice Get the total borrows of an asset
    /// @param _asset Asset address
    /// @return totalBorrow Total borrow
    function totalBorrows(address _asset) external view returns (uint256 totalBorrow) {
        totalBorrow = getVaultStorage().totalBorrows[_asset];
    }

    /// @notice Get the pause state of an asset
    /// @param _asset Asset address
    /// @return isPaused Pause state
    function paused(address _asset) external view returns (bool isPaused) {
        isPaused = getVaultStorage().paused[_asset];
    }

    /// @notice Available balance to borrow
    /// @param _asset Asset to borrow
    /// @return amount Amount available
    function availableBalance(address _asset) external view returns (uint256 amount) {
        VaultStorage storage $ = getVaultStorage();
        amount = $.totalSupplies[_asset] - $.totalBorrows[_asset];
    }

    /// @notice Utilization rate of an asset
    /// @dev Utilization scaled by 1e27
    /// @param _asset Utilized asset
    /// @return ratio Utilization ratio
    function utilization(address _asset) external view returns (uint256 ratio) {
        ratio = VaultLogic.utilization(getVaultStorage(), _asset);
    }

    /// @notice Up to date cumulative utilization index of an asset
    /// @dev Utilization scaled by 1e27
    /// @param _asset Utilized asset
    /// @return index Utilization ratio index
    function currentUtilizationIndex(address _asset) external view returns (uint256 index) {
        index = VaultLogic.currentUtilizationIndex(getVaultStorage(), _asset);
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import { IDebtToken } from "../../interfaces/IDebtToken.sol";
import { IDelegation } from "../../interfaces/IDelegation.sol";

import { ILender } from "../../interfaces/ILender.sol";
import { IOracle } from "../../interfaces/IOracle.sol";
import { IVault } from "../../interfaces/IVault.sol";
import { AgentConfiguration } from "./configuration/AgentConfiguration.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title View Logic
/// @author kexley, @capLabs
/// @notice View functions to see the state of an agent's health
library ViewLogic {
    using AgentConfiguration for ILender.AgentConfigurationMap;

    /// @notice Calculate the agent data
    /// @param $ Lender storage
    /// @param _agent Agent address
    /// @return totalDelegation Total delegation of an agent in USD, encoded with 8 decimals
    /// @return totalDebt Total debt of an agent in USD, encoded with 8 decimals
    /// @return ltv Loan to value ratio, encoded in ray (1e27)
    /// @return liquidationThreshold Liquidation ratio of an agent, encoded in ray (1e27)
    /// @return health Health status of an agent, encoded in ray (1e27)
    function agent(ILender.LenderStorage storage $, address _agent)
        public
        view
        returns (uint256 totalDelegation, uint256 totalDebt, uint256 ltv, uint256 liquidationThreshold, uint256 health)
    {
        totalDelegation = IDelegation($.delegation).coverage(_agent);
        liquidationThreshold = IDelegation($.delegation).liquidationThreshold(_agent);

        for (uint256 i; i < $.reservesCount; ++i) {
            if (!$.agentConfig[_agent].isBorrowing(i)) {
                continue;
            }

            address asset = $.reservesList[i];
            (uint256 assetPrice,) = IOracle($.oracle).getPrice(asset);
            if (assetPrice == 0) continue;

            totalDebt += (
                IERC20($.reservesData[asset].principalDebtToken).balanceOf(_agent)
                    + IERC20($.reservesData[asset].interestDebtToken).balanceOf(_agent)
                    + IERC20($.reservesData[asset].restakerDebtToken).balanceOf(_agent)
            ) * assetPrice / (10 ** $.reservesData[asset].decimals);
        }

        ltv = totalDelegation == 0 ? 0 : (totalDebt * 1e27) / totalDelegation;
        health = totalDebt == 0 ? type(uint256).max : (totalDelegation * liquidationThreshold) / totalDebt;
    }

    /// @notice Calculate the maximum amount that can be borrowed for a given asset
    /// @param $ Lender storage
    /// @param _agent Agent address
    /// @param _asset Asset to borrow
    /// @return maxBorrowableAmount Maximum amount that can be borrowed in asset decimals
    function maxBorrowable(ILender.LenderStorage storage $, address _agent, address _asset)
        external
        view
        returns (uint256 maxBorrowableAmount)
    {
        (uint256 totalDelegation, uint256 totalDebt,,, uint256 health) = agent($, _agent);

        // health is below liquidation threshold, no borrowing allowed
        if (health < 1e27) return 0;

        uint256 ltv = IDelegation($.delegation).ltv(_agent);
        uint256 borrowCapacity = totalDelegation * ltv / 1e27;

        //  already at or above borrow capacity
        if (totalDebt >= borrowCapacity) return 0;

        // Calculate remaining borrow capacity in USD (8 decimals)
        uint256 remainingCapacity = borrowCapacity - totalDebt;

        // Convert to asset amount using price and decimals
        (uint256 assetPrice,) = IOracle($.oracle).getPrice(_asset);
        if (assetPrice == 0) return 0;

        uint256 assetDecimals = $.reservesData[_asset].decimals;
        maxBorrowableAmount = remainingCapacity * (10 ** assetDecimals) / assetPrice;

        // Get total available assets using the vault's availableBalance function
        uint256 totalAvailable = IVault($.reservesData[_asset].vault).availableBalance(_asset);

        // Limit maxBorrowableAmount by total available assets
        if (totalAvailable < maxBorrowableAmount) {
            maxBorrowableAmount = totalAvailable;
        }
    }

    /// @notice Calculate the maximum amount that can be liquidated for a given asset
    /// @param $ Lender storage
    /// @param _agent Agent address
    /// @param _asset Asset to liquidate
    /// @return maxLiquidatableAmount Maximum amount that can be liquidated in asset decimals
    function maxLiquidatable(ILender.LenderStorage storage $, address _agent, address _asset)
        external
        view
        returns (uint256 maxLiquidatableAmount)
    {
        (uint256 totalDelegation, uint256 totalDebt,, uint256 liquidationThreshold, uint256 health) = agent($, _agent);
        if (health >= 1e27) return 0;

        (uint256 assetPrice,) = IOracle($.oracle).getPrice(_asset);
        if (assetPrice == 0) return 0;

        uint256 decPow = 10 ** $.reservesData[_asset].decimals;
        uint256 a = ($.targetHealth * totalDebt);
        uint256 b = (totalDelegation * liquidationThreshold);
        uint256 c = ($.targetHealth - liquidationThreshold);
        uint256 d = assetPrice;
        uint256 e = b > a ? 0 : (a - b);
        uint256 f = (c * d);
        uint256 g = e * decPow;

        maxLiquidatableAmount = g / f;
    }

    /// @notice Get the current debt balances for an agent for a specific asset
    /// @param $ Lender storage
    /// @param _agent Agent address to check debt for
    /// @param _asset Asset to check debt for
    /// @return principalDebt Principal debt amount in asset decimals
    /// @return interestDebt Interest debt amount in asset decimals
    /// @return restakerDebt Restaker debt amount in asset decimals
    function debt(ILender.LenderStorage storage $, address _agent, address _asset)
        external
        view
        returns (uint256 principalDebt, uint256 interestDebt, uint256 restakerDebt)
    {
        ILender.ReserveData memory reserve = $.reservesData[_asset];
        principalDebt = IERC20(reserve.principalDebtToken).balanceOf(_agent);
        restakerDebt = IERC20(reserve.restakerDebtToken).balanceOf(_agent);
        interestDebt = IERC20(reserve.interestDebtToken).balanceOf(_agent);
    }
}


Vulnerability type: CWE-707|CWE-240