LVLidoVault

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/LVLidoVault.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest
pragma solidity ^0.8.20;

import {IERC20} from "@balancer/solidity-utils/openzeppelin/IERC20.sol";
import {IERC20Pool} from "./interfaces/pool/erc20/IERC20Pool.sol";
import {IPoolInfoUtils} from "./interfaces/IPoolInfoUtils.sol";
import {ILVToken} from "./interfaces/ILVToken.sol";
import {ILiquidationProxy} from "./interfaces/ILiquidationProxy.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {VaultLib} from "./libraries/VaultLib.sol";
import {IWsteth} from "./interfaces/vault/IWsteth.sol";
import {IWeth} from "./interfaces/vault/IWeth.sol";
import {ISteth} from "./interfaces/vault/ISteth.sol";
import {IFlashLoanRecipient} from "@balancer/vault/IFlashLoanRecipient.sol";
import {IVault} from "@balancer/vault/IVault.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {UD60x18} from "@prb/math/UD60x18.sol";
import {IPoolDataProvider} from "./interfaces/IPoolDataProvider.sol";

/// @author Lendvest Labs
contract LVLidoVault is IFlashLoanRecipient, Ownable {
    ILVToken public testCollateralToken;
    ILVToken public testQuoteToken;

    IERC20Pool public pool;
    ILiquidationProxy public liquidationProxy;
    IVault public constant vault = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    ISteth public constant steth = ISteth(VaultLib.STETH_ADDRESS);
    IWsteth public constant wsteth = IWsteth(VaultLib.COLLATERAL_TOKEN);
    IPoolInfoUtils public constant poolInfoUtils = IPoolInfoUtils(0x30c5eF2997d6a882DE52c4ec01B6D0a5e5B4fAAE);
    IERC4626 public constant flagshipVault = IERC4626(0x38989BBA00BDF8181F4082995b3DEAe96163aC5D);
    IPoolDataProvider public constant poolDataProvider = IPoolDataProvider(0x497a1994c46d4f6C864904A9f1fac6328Cb7C8a6);

    address public LVLidoVaultUtil;
    uint256 public totalBorrowAmount;
    uint256 public totalLenderQTUnutilized;
    uint256 public flagshipVaultShares;
    uint256 public totalLenderQTUtilized;
    uint256 public totalBorrowerCT;
    uint256 public totalBorrowerCTUnutilized;
    uint256 public totalCollateralLenderCT;
    uint256 public totalCLDepositsUnutilized;
    uint256 public totalCLDepositsUtilized;
    uint256 public currentBucketIndex;
    uint256 public epochStartRedemptionRate;
    uint256 public currentRedemptionRate;
    uint256 public collateralLenderTraunche = 0;
    uint256 inverseBorrowCLAmount = 2;
    uint256 public epoch = 0;
    uint256 public epochStart;
    uint256 public termDuration = 12 hours;
    uint256 public lastEpochEnd;
    uint256 public constant epochCoolDownPeriod = 1 weeks;
    uint256 public deploymentTimestamp;
    uint256 public rate = 0;
    int256 public constant priceDifferencethreshold = -1e16; // -1%
    bool public epochStarted;
    bool private _borrowInitiated;
    bool public fundsQueued;

    VaultLib.LenderOrder[] public lenderOrders;
    VaultLib.BorrowerOrder[] public borrowerOrders;
    VaultLib.CollateralLenderOrder[] public collateralLenderOrders;
    mapping(uint256 => VaultLib.MatchInfo[]) public epochToMatches;
    mapping(uint256 => VaultLib.CollateralLenderOrder[]) public epochToCollateralLenderOrders;
    mapping(uint256 => bytes) public epochToRatereceipt;
    
    
    uint256 public requestId;
    uint256 public totalManualRepay;

    bool internal locked;

    event RateUpdated(uint256 rate);

    /**
     * @notice Constructor
     * @param _pool The address of the Ajna Pool contract
     * @param _liquidationProxy The address of the Liquidation Proxy contract
     */
    constructor(address _pool, address _liquidationProxy) Ownable(msg.sender) {
        pool = IERC20Pool(_pool);
        liquidationProxy = ILiquidationProxy(_liquidationProxy);
        testCollateralToken = ILVToken(pool.collateralAddress());
        testQuoteToken = ILVToken(pool.quoteTokenAddress());
        deploymentTimestamp = block.timestamp;
        (,,,,, uint256 liquidityRate, uint256 variableBorrowRate,,,,,) = poolDataProvider.getReserveData(VaultLib.QUOTE_TOKEN);
        rate = ((liquidityRate + variableBorrowRate) / 2);

    }

    receive() external payable {
        if (msg.sender != address(VaultLib.LIDO_WITHDRAWAL) && msg.sender != VaultLib.QUOTE_TOKEN) {
            revert VaultLib.Unauthorized();
        }
        emit VaultLib.FundsAdded(msg.value, address(this).balance, msg.sender);
    }

    fallback() external payable {
        revert VaultLib.Unauthorized();
    }

    modifier lock() {
        if (locked) revert VaultLib.ReentrantCall();
        locked = true;
        _;
        locked = false;
    }

    modifier onlyProxy() {
        if (msg.sender != LVLidoVaultUtil && msg.sender != address(liquidationProxy)) {
            revert VaultLib.OnlyProxy();
        }
        _;
    }

    function maxFundsLimiter(uint256 quoteAmount, uint256 collateralAmount) public view returns (bool) {
        uint256 wethPrice = 2430;
        uint256 wstethPrice = (wethPrice * wsteth.stEthPerToken()) / 1e18;
        // Fetch total existing lender amounts, also consider the value of the vault shares (in WETH)
        uint256 totalQT =
            quoteAmount + totalLenderQTUtilized + totalLenderQTUnutilized + flagshipVault.maxWithdraw(address(this));
        // Fetch total existing borrower amounts (in WSTETH)
        uint256 totalCT = collateralAmount + totalBorrowerCT + totalCLDepositsUtilized + totalCLDepositsUnutilized;
        uint256 totalValue = ((totalQT * wethPrice) / 1e18) + ((totalCT * wstethPrice) / 1e18);
        return totalValue <= 10000;
    }

    /**
     * @notice Converts WETH to WSTETH through a series of token conversions
     * @dev Restricted to Balancer vault and LVLidoVaultUtil contract only
     * @dev Conversion flow:
     *      1. WETH -> ETH (unwrap)
     *      2. ETH -> stETH (stake with Lido)
     *      3. stETH -> wstETH (wrap)
     * @param amount Amount of WETH to convert
     * @return uint256 Amount of WSTETH received after conversion
     * @custom:security Only callable by authorized contracts
     */
    function wethToWsteth(uint256 amount) public returns (uint256) {
        // Validate caller authorization
        if (msg.sender != address(vault) && msg.sender != address(LVLidoVaultUtil)) {
            revert VaultLib.Unauthorized();
        }

        // Step 1: Unwrap WETH to ETH
        if (!IERC20(VaultLib.QUOTE_TOKEN).approve(address(VaultLib.QUOTE_TOKEN), amount)) {
            revert VaultLib.TokenOperationFailed();
        }
        IWeth(VaultLib.QUOTE_TOKEN).withdraw(amount);

        // Step 2: Stake ETH with Lido to receive stETH
        uint256 shares = steth.submit{value: amount}(address(0));
        uint256 stethReceived = steth.getPooledEthByShares(shares);

        // Step 3: Wrap stETH to wstETH
        if (!IERC20(address(steth)).approve(address(VaultLib.COLLATERAL_TOKEN), stethReceived)) {
            revert VaultLib.TokenOperationFailed();
        }

        return wsteth.wrap(stethReceived);
    }

    /**
     * @notice Handles the flash loan callback from Balancer Vault
     * @dev This function is called by the Balancer Vault after a flash loan is initiated
     * @dev It performs the following steps:
     *      1. Validates the caller and loan authorization
     *      2. Mints collateral tokens for the pool
     *      3. Draws debt from the Ajna pool
     *      4. Converts borrowed WETH to wstETH to repay the flash loan
     * @param tokens The addresses of the tokens received in the flash loan
     * @param amounts The amounts of tokens received
     * @param feeAmounts The fee amounts charged by Balancer for the flash loan
     * @param userData Encoded data containing borrowing parameters
     */
    function receiveFlashLoan(
        IERC20[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external override {
        if (msg.sender != address(vault) || !_borrowInitiated) revert VaultLib.Unauthorized();
        _borrowInitiated = false;
        // Decode the user data to get borrowing parameters
        (uint256 baseLoanCollateral, uint256 amountToBorrow) = abi.decode(userData, (uint256, uint256));

        // Calculate total collateral (base + flash loan amount)
        uint256 totalCollateral = baseLoanCollateral + amounts[0];
        emit VaultLib.LoanComposition(baseLoanCollateral, amounts[0], totalCollateral, amountToBorrow);

        // Mint test collateral tokens and approve them for the pool
        if (
            !testCollateralToken.mint(address(this), totalCollateral)
                || !testCollateralToken.approve(address(pool), totalCollateral)
        ) {
            revert VaultLib.TokenOperationFailed();
        }

        // Record the borrowed amount for accounting purposes
        totalBorrowAmount = amountToBorrow;

        // Draw debt from the Ajna pool using the collateral
        pool.drawDebt(address(this), amountToBorrow, currentBucketIndex, totalCollateral);

        // Burn test quote tokens (accounting for the borrowed amount)
        if (!testQuoteToken.burn(address(this), amountToBorrow)) {
            revert VaultLib.TokenOperationFailed();
        }

        // Convert WETH to wstETH to repay the flash loan
        uint256 wstethReceived = wethToWsteth(amountToBorrow);

        // Repay the flash loan with fees
        for (uint256 i = 0; i < tokens.length; i++) {
            uint256 totalRepayment = amounts[i] + feeAmounts[i];
            if (!tokens[i].transfer(address(vault), totalRepayment)) {
                revert VaultLib.TokenOperationFailed();
            }
        }
    }

    /**
     * @notice Matches pending lender, borrower, and collateral lender orders.
     * @dev Creates loans by matching orders from the respective order books. Handles partial fulfillment by scaling
     * orders. Utilizes 97.5% of lender funds for loans, reserving 2.5% for interest. Supports lender funds from
     * direct deposits and flagship vault shares. Backs 50% of new debt with collateral lender deposits. Finally,
     * it initiates a flash loan to provide leverage for borrowers.
     */
    function tryMatchOrders() internal {
        uint256 initialUtilization = 975e15; // 97.5%
        uint256 originationFactor = poolInfoUtils.borrowFeeRate(address(pool));
        uint256 depositFeeRate = poolInfoUtils.depositFeeRate(address(pool));
        if (totalCollateralLenderCT == 0) {
            revert VaultLib.InsufficientFunds();
        }
        uint256 totalPreFeeAmountToDeposit = 0;
        uint256 borrowerCTMatched = 0;
        uint256 borrowAmountMatched = 0;
        uint256 totalSharesToRedeem = 0;

        for ((uint256 i, uint256 j) = (0, 0); i < lenderOrders.length && j < borrowerOrders.length;) {
            // Calculate borrower's debt requirements
            uint256 flashLoanAmount = borrowerOrders[j].collateralAmount * (VaultLib.leverageFactor - 10);
            uint256 amountToBorrow = (flashLoanAmount * epochStartRedemptionRate) / 1e19;
            uint256 borrowFee = (amountToBorrow * originationFactor) / 1e18;

            // Skip micro amounts from borrowers
            if (borrowFee == 0) {
                j++;
                continue;
            }
            uint256 t0Debt = amountToBorrow + borrowFee;
            uint256 maxLenderAmount = lenderOrders[i].quoteAmount;
            // Check lenderOrder balances, using native amounts for further processing
            if (lenderOrders[i].vaultShares > 0) {
                maxLenderAmount = flagshipVault.previewRedeem(lenderOrders[i].vaultShares);
            } else if (lenderOrders[i].quoteAmount > 0) {
                maxLenderAmount = lenderOrders[i].quoteAmount;
            } else {
                i++;
                continue;
            }

            // Skip micro amounts from lenders
            uint256 maxLenderQuoteAmountFee = (maxLenderAmount * depositFeeRate) / 1e18;
            if (maxLenderQuoteAmountFee == 0) {
                i++;
                continue;
            }
            uint256 maxT0Debt = (initialUtilization * (maxLenderAmount - maxLenderQuoteAmountFee)) / 1e18;

            uint256 scaledT0Debt;
            uint256 scaledAmountToBorrow;
            uint256 scaledBorrowerCollateralAmount;
            uint256 preFeeAmountToDeposit;

            // Scale down values based on max lender deposit amount
            if (t0Debt > maxT0Debt) {
                // If borrower needs more than lender can provide, scale down the borrower's order
                scaledT0Debt = maxT0Debt;
                scaledAmountToBorrow = (scaledT0Debt * 1e18) / (1e18 + originationFactor);
                uint256 scalingFactor = (scaledAmountToBorrow * 1e18) / amountToBorrow;
                scaledBorrowerCollateralAmount = (borrowerOrders[j].collateralAmount * scalingFactor) / 1e18;
                preFeeAmountToDeposit = maxLenderAmount;

                // Skip lender if scaling causes 0 values
                if (scaledBorrowerCollateralAmount == 0 || preFeeAmountToDeposit == 0) {
                    i++;
                    continue;
                }
            }
            // Lender deposit is enough to support borrower order
            else {
                scaledT0Debt = t0Debt;
                scaledAmountToBorrow = amountToBorrow;
                scaledBorrowerCollateralAmount = borrowerOrders[j].collateralAmount;
                preFeeAmountToDeposit = (scaledT0Debt * 1e36) / (initialUtilization * (1e18 - depositFeeRate));
            }

            // Amount set aside by lender for interest (with 97.5% initial utilization, 2.5% is set aside)
            uint256 scaledReservedQuoteAmount =
                ((1e18 - initialUtilization) * ((scaledT0Debt * 1e18) / initialUtilization)) / 1e18;

            // Create a match
            epochToMatches[epoch].push(
                VaultLib.MatchInfo({
                    lender: lenderOrders[i].lender,
                    borrower: borrowerOrders[j].borrower,
                    quoteAmount: scaledT0Debt, // 97.5% of the quote token amount
                    collateralAmount: scaledBorrowerCollateralAmount,
                    reservedQuoteAmount: scaledReservedQuoteAmount // 2.5% of the quote token amount
                })
            );
            // Reduce available deposits for user
            if (lenderOrders[i].quoteAmount == 0 && lenderOrders[i].vaultShares > 0) {
                // Values were scaled down so we need to redeem less shares than available
                if (maxLenderAmount > preFeeAmountToDeposit) {
                    // Scale down the shares to be used, preview doesn't matter here since we'll aggregate the Morpho withdrawals
                    uint256 scalingFactor = (preFeeAmountToDeposit * 1e18) / maxLenderAmount;
                    uint256 sharesToRedeem = (lenderOrders[i].vaultShares * scalingFactor) / 1e18;
                    totalSharesToRedeem += sharesToRedeem;
                    lenderOrders[i].vaultShares -= sharesToRedeem;
                }
                // Exact value used so we need to redeem exact shares
                else {
                    totalSharesToRedeem += lenderOrders[i].vaultShares;
                    lenderOrders[i].vaultShares = 0;
                }
            } else {
                lenderOrders[i].quoteAmount -= preFeeAmountToDeposit;
            }

            // Account for totals and adjust borrowerOrder
            totalPreFeeAmountToDeposit += preFeeAmountToDeposit;
            borrowerCTMatched += scaledBorrowerCollateralAmount;
            borrowAmountMatched += scaledAmountToBorrow;
            borrowerOrders[j].collateralAmount -= scaledBorrowerCollateralAmount;

            // Remove used up lenderOrders
            if (lenderOrders[i].quoteAmount == 0 && lenderOrders[i].vaultShares == 0) {
                // Only do this when size is 2
                if (lenderOrders.length > 2) {
                    // Move all the elements to the left
                    for (uint256 k = i; k < lenderOrders.length - 1; k++) {
                        lenderOrders[k] = lenderOrders[k + 1];
                    }
                    lenderOrders.pop();
                } else if (lenderOrders.length > 0) {
                    lenderOrders[i] = lenderOrders[lenderOrders.length - 1];
                    lenderOrders.pop();
                }
            }

            // Remove used up borrowerOrders
            if (borrowerOrders[j].collateralAmount == 0) {
                // Remove fully utilized borrower order
                if (borrowerOrders.length > 2) {
                    // Move all the elements to the left
                    for (uint256 k = j; k < borrowerOrders.length - 1; k++) {
                        borrowerOrders[k] = borrowerOrders[k + 1];
                    }
                    borrowerOrders.pop();
                } else if (borrowerOrders.length > 0) {
                    borrowerOrders[j] = borrowerOrders[borrowerOrders.length - 1];
                    borrowerOrders.pop();
                }
            }
        }

        // Ensure collateral lender deposits are sufficient before processing their orders
        if (((borrowAmountMatched * 1e18) / epochStartRedemptionRate) / inverseBorrowCLAmount > totalCollateralLenderCT)
        {
            revert VaultLib.InsufficientFunds();
        }

        uint256 collateralLenderDepositsToMatch =
            ((borrowAmountMatched * 1e18) / epochStartRedemptionRate) / inverseBorrowCLAmount;

        // Match collateral lender orders for the epoch
        for (uint256 i = 0; i < collateralLenderOrders.length && collateralLenderDepositsToMatch > 0;) {
            // Take as much as needed from each collateral lender
            uint256 collateralUtilized = collateralLenderOrders[i].collateralAmount < collateralLenderDepositsToMatch
                ? collateralLenderOrders[i].collateralAmount
                : collateralLenderDepositsToMatch;
            collateralLenderDepositsToMatch -= collateralUtilized;
            // Create collateralLenderOrder for the epoch
            epochToCollateralLenderOrders[epoch].push(
                VaultLib.CollateralLenderOrder({
                    collateralLender: collateralLenderOrders[i].collateralLender,
                    collateralAmount: collateralUtilized
                })
            );

            // Remove or update the collateral lender order
            if (collateralUtilized == collateralLenderOrders[i].collateralAmount) {
                // Remove fully utilized order
                for (uint256 k = i; k < collateralLenderOrders.length - 1; k++) {
                    collateralLenderOrders[k] = collateralLenderOrders[k + 1];
                }
                collateralLenderOrders.pop();
            } else {
                // Reduce partially utilized order
                collateralLenderOrders[i].collateralAmount -= collateralUtilized;
            }
        }

        // Adjust global collateral lender totals
        totalCLDepositsUnutilized += (((borrowAmountMatched * 1e18) / epochStartRedemptionRate) / inverseBorrowCLAmount);
        totalCollateralLenderCT -= totalCLDepositsUnutilized;

        // Withdraw Morpho shares
        flagshipVaultShares -= totalSharesToRedeem;
        uint256 maxAssets = flagshipVault.redeem(totalSharesToRedeem, address(this), address(this));

        // Deposit lender utilized amounts
        if (
            !testQuoteToken.mint(address(this), totalPreFeeAmountToDeposit)
                || !IERC20(pool.quoteTokenAddress()).approve(address(pool), totalPreFeeAmountToDeposit)
        ) {
            revert VaultLib.TokenOperationFailed();
        }
        (, uint256 totalPostFeeDepositAmount) =
            pool.addQuoteToken(totalPreFeeAmountToDeposit, currentBucketIndex, block.timestamp + 3600);

        // Note Accounting, lender fees deducted here
        totalLenderQTUtilized += totalPostFeeDepositAmount;
        totalLenderQTUnutilized -= totalPreFeeAmountToDeposit;

        // Build params and execute flash loan
        IERC20[] memory tokens = new IERC20[](1);
        tokens[0] = IERC20(VaultLib.COLLATERAL_TOKEN);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = (borrowerCTMatched * (VaultLib.leverageFactor - 10)) / 10;

        // Lock flash loan
        _borrowInitiated = true;
        vault.flashLoan(this, tokens, amounts, abi.encode(borrowerCTMatched, borrowAmountMatched));
        totalBorrowerCTUnutilized -= borrowerCTMatched;
    }

    /**
     * @notice Starts a new epoch, matching orders and managing funds.
     * @dev Validates epoch conditions, sets the redemption rate, and then calls `tryMatchOrders()` to match pending
     * lender and borrower orders. Any remaining unutilized lender funds are deposited into the flagship vault, and
     * the corresponding shares are distributed to the lenders.
     */
    function startEpoch() external lock {
        // Validate epoch parameters
        if (
            lastEpochEnd + epochCoolDownPeriod > block.timestamp || epochStarted
                || deploymentTimestamp + 2 hours > block.timestamp
        ) {
            revert VaultLib.Unauthorized();
        }

        // Get current wstETH/stETH ratio and set rates
        uint256 redemptionRate = wsteth.stEthPerToken();
        epochStartRedemptionRate = redemptionRate;
        currentRedemptionRate = redemptionRate;
        currentBucketIndex = poolInfoUtils.priceToIndex(redemptionRate);

        // Update the rate
        (,,,,, uint256 liquidityRate, uint256 variableBorrowRate,,,,,) = poolDataProvider.getReserveData(VaultLib.QUOTE_TOKEN);
        rate = ((liquidityRate + variableBorrowRate) / 2);
        epoch++;
        // Update state first
        epoch++;
        epochStarted = true;
        collateralLenderTraunche = 0;
        epochStart = block.timestamp;

        // Emit events before external calls
        emit VaultLib.EpochStarted(epoch, epochStart, epochStart + termDuration);
        emit VaultLib.RedemptionRateUpdated(redemptionRate);

        // Perform external calls
        tryMatchOrders();

        // Store amount to deposit in temporary variable
        uint256 amountToDeposit = totalLenderQTUnutilized;

        // Deposit remaining unutilized lender funds into flagship vault
        if (!IERC20(VaultLib.QUOTE_TOKEN).approve(address(flagshipVault), amountToDeposit)) {
            revert VaultLib.TokenOperationFailed();
        }
        uint256 depositedShares = flagshipVault.deposit(amountToDeposit, address(this));
        flagshipVaultShares += depositedShares;

        // Update lender shares after deposit using PRBMath
        for (uint256 i = 0; i < lenderOrders.length; i++) {
            UD60x18 lenderAmount = UD60x18.wrap(lenderOrders[i].quoteAmount);
            UD60x18 totalShares = UD60x18.wrap(flagshipVaultShares);
            UD60x18 totalDeposit = UD60x18.wrap(amountToDeposit);

            // Calculate shares using PRBMath: (lenderAmount * totalShares) / totalDeposit
            UD60x18 shares = lenderAmount.mul(totalShares).div(totalDeposit);
            lenderOrders[i].vaultShares = UD60x18.unwrap(shares);
            lenderOrders[i].quoteAmount = 0;
        }
        totalLenderQTUnutilized = 0;
    }

    // PROXY FUNCTIONS
    /**
     * @notice Pushes a lender order to the lenderOrders array.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param lenderOrder The lender order to push.
     */
    function lenderOrdersPush(VaultLib.LenderOrder memory lenderOrder) external onlyProxy {
        lenderOrders.push(lenderOrder);
    }

    /**
     * @notice Pushes a borrower order to the borrowerOrders array.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param borrowerOrder The borrower order to push.
     */
    function borrowerOrdersPush(VaultLib.BorrowerOrder memory borrowerOrder) external onlyProxy {
        borrowerOrders.push(borrowerOrder);
    }

    /**
     * @notice Pushes a collateral lender order to the collateralLenderOrders array.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param collateralLenderOrder The collateral lender order to push.
     */
    function collateralLenderOrdersPush(VaultLib.CollateralLenderOrder memory collateralLenderOrder)
        external
        onlyProxy
    {
        collateralLenderOrders.push(collateralLenderOrder);
    }

    /**
     * @notice Pushes a match info to the epochToMatches mapping.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param epoch The epoch number.
     * @param matchInfo The match info to push.
     */
    function epochToMatchesPush(uint256 epoch, VaultLib.MatchInfo memory matchInfo) external onlyProxy {
        epochToMatches[epoch].push(matchInfo);
    }

    /**
     * @notice Pushes a collateral lender order to the epochToCollateralLenderOrders mapping.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param epoch The epoch number.
     * @param collateralLenderOrder The collateral lender order to push.
     */
    function epochToCollateralLenderOrdersPush(
        uint256 epoch,
        VaultLib.CollateralLenderOrder memory collateralLenderOrder
    ) external onlyProxy {
        epochToCollateralLenderOrders[epoch].push(collateralLenderOrder);
    }

    /**
     * @notice Deletes the matches for a given epoch.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param epoch The epoch number.
     */
    function deleteEpochMatches(uint256 epoch) external onlyProxy {
        delete epochToMatches[epoch];
    }

    /**
     * @notice Deletes the collateral lender orders for a given epoch.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param epoch The epoch number.
     */
    function deleteEpochCollateralLenderOrders(uint256 epoch) external onlyProxy {
        delete epochToCollateralLenderOrders[epoch];
    }

    /**
     * @notice Approves a token for the proxy contract.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param token The address of the token to approve.
     * @param receiver The address of the receiver.
     * @param amount The amount to approve.
     */
    function approveForProxy(address token, address receiver, uint256 amount) external onlyProxy returns (bool) {
        return IERC20(token).approve(receiver, amount);
    }

    /**
     * @notice Transfers a token on behalf of the proxy contract.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param token The address of the token to transfer.
     * @param recipient The address of the recipient.
     * @param amount The amount to transfer.
     */
    function transferForProxy(address token, address recipient, uint256 amount) external onlyProxy returns (bool) {
        return IERC20(token).transfer(recipient, amount);
    }

    /**
     * @notice Requests withdrawals for WstETH from Lido protocol
     * @dev This function initiates the withdrawal process for wstETH tokens
     * @dev Only callable by the proxy contract (LVLidoVaultUtil or LiquidationProxy)
     * @dev Sets the global requestId to the first ID returned from the withdrawal request
     * @dev Marks funds as queued in the Lido withdrawal queue
     * @dev The withdrawal process in Lido is asynchronous - first request, then claim after finalization
     * @param amounts Array of wstETH amounts to withdraw from Lido protocol
     * @return requestId The ID of the first withdrawal request, used later for claiming
     */
    function requestWithdrawalsWstETH(uint256[] memory amounts) external onlyProxy returns (uint256) {
        uint256[] memory requestIds = VaultLib.LIDO_WITHDRAWAL.requestWithdrawalsWstETH(amounts, address(this));
        requestId = requestIds[0];
        fundsQueued = true;
        return requestId;
    }

    /**
     * @notice Claims a withdrawal from Lido protocol
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @dev Uses the global requestId that was set during the requestWithdrawalsWstETH call
     * @dev This function should only be called after the Lido withdrawal request has been finalized
     * @dev After successful claim, the ETH will be sent to this contract's address
     */
    function claimWithdrawal() external onlyProxy {
        // Call Lido withdrawal contract to claim the finalized withdrawal using stored requestId
        VaultLib.LIDO_WITHDRAWAL.claimWithdrawal(requestId);
        // Note: After claiming, the contract will receive ETH which should be converted to WETH if needed
    }

    /**
     * @notice Deposits ETH for WETH.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param amount The amount of ETH to deposit.
     */
    function depositEthForWeth(uint256 amount) external onlyProxy {
        IWeth(VaultLib.QUOTE_TOKEN).deposit{value: amount}();
    }

    /**
     * @notice Ends the current epoch.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @dev This function resets epoch state variables and prepares the vault for the next epoch.
     */
    function end_epoch() external onlyProxy {
        epochStarted = false;
        fundsQueued = false; // Reset withdrawal queue status
        lastEpochEnd = block.timestamp;

        emit VaultLib.EndEpoch(lastEpochEnd);
    }

    /**
     * @notice Clears Ajna deposits.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param amount The amount of quote token to remove.
     */
    function clearAjnaDeposits(uint256 amount) external onlyProxy lock {
        (uint256 removedAmount_,) = pool.removeQuoteToken(amount, currentBucketIndex);
        if (!testQuoteToken.burn(address(this), removedAmount_)) {
            revert VaultLib.TokenOperationFailed();
        }
    }

    /**
     * @notice Sets the total manual repay amount.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param amount The total manual repay amount.
     */
    function setTotalManualRepay(uint256 amount) external onlyProxy {
        totalManualRepay = amount;
    }

    /**
     * @notice Adds collateral to avoid liquidation.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @dev Uses unutilized collateral lender deposits to increase position health.
     * @param amount The amount of collateral to add.
     */
    function avoidLiquidation(uint256 amount) external onlyProxy lock {
        // Check if we have enough unutilized collateral from lenders
        if (totalCLDepositsUnutilized < amount) revert VaultLib.InsufficientFunds();

        // Update state first
        totalCLDepositsUnutilized -= amount;
        totalCLDepositsUtilized += amount;

        // Emit event before external calls
        emit VaultLib.AvoidLiquidation(amount);

        // Perform external calls last
        if (!testCollateralToken.mint(address(this), amount) || !testCollateralToken.approve(address(pool), amount)) {
            revert VaultLib.TokenOperationFailed();
        }

        pool.drawDebt(address(this), 0, 7388, amount);
    }

    /**
     * @notice Sets the LVLidoVaultUtil address.
     * @dev Can only be called by the owner.
     * @param _LVLidoVaultUtil The new LVLidoVaultUtil address.
     */
    function setLVLidoVaultUtilAddress(address _LVLidoVaultUtil) public onlyOwner {
        emit VaultLib.LVLidoVaultUtilAddressUpdated(LVLidoVaultUtil, _LVLidoVaultUtil);
        LVLidoVaultUtil = _LVLidoVaultUtil;
    }

    // Note Anyone can call this function, ensure there are overflow protections in place.
    // Define range of values that can be entered in.
    /**
     * @notice Repays Ajna debt.
     * @param quoteTokenAmount The amount of quote token to repay.
     * @dev This function allows anyone to repay debt on behalf of the vault
     */
    function repayAjnaDebt(uint256 quoteTokenAmount) external lock {
        // Prevent zero-value transactions to save gas
        if (quoteTokenAmount == 0) {
            revert VaultLib.InvalidInput();
        }

        // Update state first
        totalManualRepay += quoteTokenAmount;

        // Perform external calls last
        if (
            !IERC20(VaultLib.QUOTE_TOKEN).transferFrom(msg.sender, address(this), quoteTokenAmount)
                || !testQuoteToken.mint(address(this), quoteTokenAmount)
                || !IERC20(address(testQuoteToken)).approve(address(pool), quoteTokenAmount)
        ) {
            revert VaultLib.TokenOperationFailed();
        }
        pool.repayDebt(address(this), quoteTokenAmount, 0, address(this), 7388);
    }

    /**
     * @notice Gets the matches for a given epoch.
     * @param _epoch The epoch number.
     * @return An array of MatchInfo structs.
     */
    function getEpochMatches(uint256 _epoch) external view returns (VaultLib.MatchInfo[] memory) {
        return epochToMatches[_epoch];
    }

    /**
     * @notice Gets the collateral lender orders for a given epoch.
     * @param _epoch The epoch number.
     * @return An array of CollateralLenderOrder structs.
     */
    function getEpochCollateralLenderOrders(uint256 _epoch)
        external
        view
        returns (VaultLib.CollateralLenderOrder[] memory)
    {
        return epochToCollateralLenderOrders[_epoch];
    }

    /**
     * @notice Sets the total CL deposits unutilized.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalCLDepositsUnutilized The new total CL deposits unutilized.
     */
    function setTotalCLDepositsUnutilized(uint256 _totalCLDepositsUnutilized) external onlyProxy {
        totalCLDepositsUnutilized = _totalCLDepositsUnutilized;
    }

    /**
     * @notice Sets the total CL deposits utilized.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalCLDepositsUtilized The new total CL deposits utilized.
     */
    function setTotalCLDepositsUtilized(uint256 _totalCLDepositsUtilized) external onlyProxy {
        totalCLDepositsUtilized = _totalCLDepositsUtilized;
    }

    /**
     * @notice Sets the collateral lender traunche.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _collateralLenderTraunche The new collateral lender traunche.
     */
    function setCollateralLenderTraunche(uint256 _collateralLenderTraunche) external onlyProxy {
        collateralLenderTraunche = _collateralLenderTraunche;
    }

    /**
     * @notice Sets the current redemption rate.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _currentRedemptionRate The new current redemption rate.
     */
    function setCurrentRedemptionRate(uint256 _currentRedemptionRate) external onlyProxy {
        currentRedemptionRate = _currentRedemptionRate;
    }

    /**
     * @notice Mints tokens for the proxy contract.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param token The address of the token to mint.
     * @param receiver The address of the receiver.
     * @param amount The amount to mint.
     */
    function mintForProxy(address token, address receiver, uint256 amount) external onlyProxy returns (bool) {
        if (token == address(testQuoteToken)) {
            return testQuoteToken.mint(receiver, amount);
        } else if (token == address(testCollateralToken)) {
            return testCollateralToken.mint(receiver, amount);
        }
    }

    /**
     * @notice Burns tokens for the proxy contract.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param token The address of the token to burn.
     * @param receiver The address of the receiver.
     * @param amount The amount to burn.
     */
    function burnForProxy(address token, address receiver, uint256 amount) external onlyProxy returns (bool) {
        if (token == address(testQuoteToken)) {
            return testQuoteToken.burn(receiver, amount);
        } else if (token == address(testCollateralToken)) {
            return testCollateralToken.burn(receiver, amount);
        }
    }

    /**
     * @notice Repays debt for the proxy contract.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param debt The amount of debt to repay.
     * @param collateral The amount of collateral to repay.
     */
    function repayDebtForProxy(uint256 debt, uint256 collateral) external onlyProxy {
        pool.repayDebt(address(this), debt, collateral, address(this), 7388);
    }

    /**
     * @notice Sets the total lender QT unutilized.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalLenderQTUnutilized The new total lender QT unutilized.
     */
    function setTotalLenderQTUnutilized(uint256 _totalLenderQTUnutilized) external onlyProxy {
        totalLenderQTUnutilized = _totalLenderQTUnutilized;
    }

    /**
     * @notice Sets the total lender QT utilized.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalLenderQTUtilized The new total lender QT utilized.
     */
    function setTotalLenderQTUtilized(uint256 _totalLenderQTUtilized) external onlyProxy {
        totalLenderQTUtilized = _totalLenderQTUtilized;
    }

    /**
     * @notice Sets the total borrower CT.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalBorrowerCT The new total borrower CT.
     */
    function setTotalBorrowerCT(uint256 _totalBorrowerCT) external onlyProxy {
        totalBorrowerCT = _totalBorrowerCT;
    }

    /**
     * @notice Sets the total borrower CT unutilized.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalBorrowerCTUnutilized The new total borrower CT unutilized.
     */
    function setTotalBorrowerCTUnutilized(uint256 _totalBorrowerCTUnutilized) external onlyProxy {
        totalBorrowerCTUnutilized = _totalBorrowerCTUnutilized;
    }

    /**
     * @notice Sets the total collateral lender CT.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _totalCollateralLenderCT The new total collateral lender CT.
     */
    function setTotalCollateralLenderCT(uint256 _totalCollateralLenderCT) external onlyProxy {
        totalCollateralLenderCT = _totalCollateralLenderCT;
    }

    // ORDER CREATION AND WITHDRAWAL FUNCTIONS
    /**
     * @notice Creates a lender order.
     * @param amount The amount of quote token (WETH) to lend.
     * @return The amount of quote token lent.
     * @dev This function allows users to deposit quote tokens (WETH) into the vault
     * @dev The deposited tokens are tracked as unutilized until they are matched with borrower orders
     * @dev or deposited into the flagship vault during epoch start
     */
    function createLenderOrder(uint256 amount) external lock returns (uint256) {
        // Prevent zero-value transactions to save gas
        if (amount == 0) revert VaultLib.InvalidInput();
        // if (!maxFundsLimiter(amount, 0)) revert VaultLib.MaxFundsExceeded();
        lenderOrders.push(VaultLib.LenderOrder({lender: msg.sender, quoteAmount: amount, vaultShares: 0}));
        totalLenderQTUnutilized += amount;

        emit VaultLib.LenderOrderAdded(msg.sender, amount);

        if (!IERC20(VaultLib.QUOTE_TOKEN).transferFrom(address(msg.sender), address(this), amount)) {
            revert VaultLib.TokenOperationFailed();
        }

        return amount;
    }

    /**
     * @notice Creates a new borrower order with the specified collateral amount
     * @dev Transfers collateral from the user to the vault and adds a new borrower order
     * @param collateralAmount The amount of collateral to deposit for the order
     * @return The amount of collateral deposited
     */
    function createBorrowerOrder(uint256 collateralAmount) external lock returns (uint256) {
        if (collateralAmount == 0) {
            revert VaultLib.InvalidInput();
        }
        // if (!maxFundsLimiter(0, collateralAmount)) revert VaultLib.MaxFundsExceeded();

        totalBorrowerCT += collateralAmount;
        totalBorrowerCTUnutilized += collateralAmount;
        borrowerOrders.push(VaultLib.BorrowerOrder({borrower: msg.sender, collateralAmount: collateralAmount}));

        // Emit event before external call
        emit VaultLib.BorrowerOrderAdded(msg.sender, collateralAmount);

        // Perform external call last
        if (!IERC20(VaultLib.COLLATERAL_TOKEN).transferFrom(msg.sender, address(this), collateralAmount)) {
            revert VaultLib.TokenOperationFailed();
        }

        return collateralAmount;
    }

    /**
     * @notice Lends collateral as a collateral lender.
     * @param amount The amount of collateral to lend.
     * @return The amount of collateral lent.
     * @dev This function allows users to deposit collateral tokens (wstETH) to be used as additional
     * @dev collateral for borrowers, helping to maintain healthy collateralization ratios and
     * @dev potentially preventing liquidations. Collateral lenders earn fees from this service.
     */
    function createCLOrder(uint256 amount) external lock returns (uint256) {
        // Prevent zero-value transactions to save gas
        if (amount == 0) {
            revert VaultLib.InvalidInput();
        }
        // if (!maxFundsLimiter(0, amount)) revert VaultLib.MaxFundsExceeded();

        totalCollateralLenderCT += amount;
        collateralLenderOrders.push(VaultLib.CollateralLenderOrder(msg.sender, amount));

        emit VaultLib.CollateralLenderDeposit(msg.sender, amount);

        if (!IERC20(VaultLib.COLLATERAL_TOKEN).transferFrom(msg.sender, address(this), amount)) {
            revert VaultLib.TokenOperationFailed();
        }

        return amount;
    }

    /**
     * @notice Withdraws a lender order.
     * @dev This function allows lenders to withdraw their unutilized funds from the vault.
     * @dev Handles both direct quote token deposits and flagship vault shares.
     * @dev Uses a swap-and-pop pattern to efficiently remove orders from the array.
     * @return The total amount of quote tokens withdrawn.
     */
    function withdrawLenderOrder() external lock returns (uint256) {
        uint256 userWithdrawalAmount = 0;
        uint256 userVaultShares = 0;

        // Find and collect all orders from this lender
        for (uint256 i = 0; i < lenderOrders.length;) {
            if (lenderOrders[i].lender == msg.sender) {
                // Track either vault shares or direct quote token amounts
                if (lenderOrders[i].vaultShares > 0) {
                    userVaultShares += lenderOrders[i].vaultShares;
                } else {
                    userWithdrawalAmount += lenderOrders[i].quoteAmount;
                }

                // Swap with last element and pop (more gas efficient than shifting elements)
                if (i < lenderOrders.length - 1) {
                    lenderOrders[i] = lenderOrders[lenderOrders.length - 1];
                }
                lenderOrders.pop();
            } else {
                i++;
            }
        }

        if (userWithdrawalAmount == 0 && userVaultShares == 0) {
            revert VaultLib.NoUnfilledOrdersFound();
        }

        // Update all state variables first
        totalLenderQTUnutilized -= userWithdrawalAmount;

        // Handle flagship vault shares if present
        if (userVaultShares > 0) {
            flagshipVaultShares -= userVaultShares;
        }

        // Emit event before any external calls
        emit VaultLib.WithdrawLender(msg.sender, userWithdrawalAmount);

        // Perform external calls last
        if (userVaultShares > 0) {
            userWithdrawalAmount += flagshipVault.redeem(userVaultShares, address(this), address(this));
        }

        if (!IERC20(VaultLib.QUOTE_TOKEN).transfer(msg.sender, userWithdrawalAmount)) {
            revert VaultLib.TokenOperationFailed();
        }

        return userWithdrawalAmount;
    }

    /**
     * @notice Withdraws a borrower order.
     * @dev This function allows borrowers to withdraw their unutilized collateral from pending orders
     * @dev Uses a gas-efficient removal pattern (swap with last element and pop)
     * @dev Updates global accounting for borrower collateral tracking
     * @return The total amount of collateral tokens withdrawn.
     */
    function withdrawBorrowerOrder() external lock returns (uint256) {
        uint256 userWithdrawalAmount = 0;
        for (uint256 i = 0; i < borrowerOrders.length;) {
            if (borrowerOrders[i].borrower == msg.sender) {
                userWithdrawalAmount += borrowerOrders[i].collateralAmount;
                if (i < borrowerOrders.length - 1) {
                    borrowerOrders[i] = borrowerOrders[borrowerOrders.length - 1];
                }
                borrowerOrders.pop();
            } else {
                i++;
            }
        }

        if (userWithdrawalAmount == 0) {
            revert VaultLib.NoUnfilledOrdersFound();
        }

        totalBorrowerCT -= userWithdrawalAmount;
        totalBorrowerCTUnutilized -= userWithdrawalAmount;
        emit VaultLib.WithdrawBorrower(msg.sender, userWithdrawalAmount);
        if (!IERC20(VaultLib.COLLATERAL_TOKEN).transfer(msg.sender, userWithdrawalAmount)) {
            revert VaultLib.TokenOperationFailed();
        }

        return userWithdrawalAmount;
    }

    /**
     * @notice Withdraws a collateral lender order.
     * @dev Allows collateral lenders to withdraw their unutilized collateral tokens (wstETH)
     * @dev Finds and aggregates all orders from the caller, then removes them from the array
     * @dev Updates global accounting and transfers tokens back to the lender
     * @dev Protected by the lock modifier to prevent reentrancy attacks
     * @return The total amount of collateral withdrawn
     */
    function withdrawCLOrder() external lock returns (uint256) {
        uint256 userWithdrawalAmount = 0;
        for (uint256 i = 0; i < collateralLenderOrders.length;) {
            if (collateralLenderOrders[i].collateralLender == msg.sender) {
                userWithdrawalAmount += collateralLenderOrders[i].collateralAmount;
                if (i < collateralLenderOrders.length - 1) {
                    collateralLenderOrders[i] = collateralLenderOrders[collateralLenderOrders.length - 1];
                }
                collateralLenderOrders.pop();
            } else {
                i++;
            }
        }
        if (userWithdrawalAmount == 0) {
            revert VaultLib.NoUnfilledOrdersFound();
        }

        totalCollateralLenderCT -= userWithdrawalAmount;
        emit VaultLib.WithdrawCollateralLender(msg.sender, userWithdrawalAmount);
        if (!IERC20(VaultLib.COLLATERAL_TOKEN).transfer(msg.sender, userWithdrawalAmount)) {
            revert VaultLib.TokenOperationFailed();
        }

        return userWithdrawalAmount;
    }

    // AUCTION BASED FUNCTIONS
    /**
     * @notice Sets whether kicking is allowed.
     * @dev Can only be called by the LVLidoVaultUtil or LiquidationProxy contract.
     * @param _allowKick Whether kicking is allowed.
     */
    function setAllowKick(bool _allowKick) external onlyProxy {
        liquidationProxy.setAllowKick(_allowKick);
    }

    function getAllowKick() external view returns (bool) {
        return liquidationProxy.allowKick();
    }

    function lenderKick(uint256 bondAmount) external onlyProxy {
        if (
            !(
                liquidationProxy.allowKick() == true && testQuoteToken.mint(address(this), bondAmount)
                    && IERC20(address(testQuoteToken)).approve(address(pool), bondAmount)
            )
        ) {
            revert VaultLib.TokenOperationFailed();
        }

        pool.lenderKick(currentBucketIndex, 7388);
    }

    function withdrawBondsForProxy() external onlyProxy returns (uint256) {
        (uint256 claimable, uint256 locked) = pool.kickerInfo(address(this));
        if (locked != 0) {
            revert VaultLib.LockedBonds();
        }
        return pool.withdrawBonds(address(this), claimable);
    }


    function updateRate(uint256 _rate) external onlyProxy {

        if(_rate > 0) {
            rate = _rate;
        } else {
            (,,,,, uint256 liquidityRate, uint256 variableBorrowRate,,,,,) = poolDataProvider.getReserveData(VaultLib.QUOTE_TOKEN);
            rate = ((liquidityRate + variableBorrowRate) / 2);
        }

        emit RateUpdated(rate);
    }

    function setRateReceipt(uint256 _epoch, bytes memory _rateReceipt) external  {
        epochToRatereceipt[_epoch] = _rateReceipt;
    }

    function getRateReceipt(uint256 _epoch) external view returns (bytes memory) {
        return epochToRatereceipt[_epoch];
    }
        
    
}
"
    },
    "lib/balancer-v2-monorepo/pkg/interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
"
    },
    "src/interfaces/pool/erc20/IERC20Pool.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity 0.8.20;

import {IPool} from "../IPool.sol";
import {IERC20PoolBorrowerActions} from "./IERC20PoolBorrowerActions.sol";
import {IERC20PoolLenderActions} from "./IERC20PoolLenderActions.sol";
import {IERC20PoolImmutables} from "./IERC20PoolImmutables.sol";
import {IERC20PoolEvents} from "./IERC20PoolEvents.sol";

/**
 * @title ERC20 Pool
 */
interface IERC20Pool is
    IPool,
    IERC20PoolLenderActions,
    IERC20PoolBorrowerActions,
    IERC20PoolImmutables,
    IERC20PoolEvents
{
    /**
     *  @notice Initializes a new pool, setting initial state variables.
     *  @param  rate_ Initial interest rate of the pool (min accepted value 1%, max accepted value 10%).
     */
    function initialize(uint256 rate_) external;

    /**
     *  @notice Returns the minimum amount of collateral an actor may have in a bucket.
     *  @param  bucketIndex_ The bucket index for which the dust limit is desired, or `0` for pledged collateral.
     *  @return The dust limit for `bucketIndex_`.
     */
    function bucketCollateralDust(uint256 bucketIndex_) external pure returns (uint256);
}
"
    },
    "src/interfaces/IPoolInfoUtils.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity ^0.8.20;

/**
 *  @title  Pool Info Utils contract
 *  @notice Contract for providing information for any deployed pool.
 *  @dev    Pool info is calculated using same helper functions / logic as in `Pool` contracts.
 */
interface IPoolInfoUtils {
    /**
     *  @notice Exposes status of a liquidation auction.
     *  @param  ajnaPool_         Address of `Ajna` pool.
     *  @param  borrower_         Identifies the loan being liquidated.
     *  @return kickTime_         Time auction was kicked, implying end time.
     *  @return collateral_       Remaining collateral available to be purchased.               (`WAD`)
     *  @return debtToCover_      Borrower debt to be covered.                                  (`WAD`)
     *  @return isCollateralized_ `True` if loan is collateralized.
     *  @return price_            Current price of the auction.                                 (`WAD`)
     *  @return neutralPrice_     Price at which bond holder is neither rewarded nor penalized. (`WAD`)
     *  @return referencePrice_   Price used to determine auction start price.                  (`WAD`)
     *  @return debtToCollateral_ Borrower debt to collateral at time of kick.                  (`WAD`)
     *  @return bondFactor_       The factor used for calculating bond size.                    (`WAD`)
     */
    function auctionStatus(address ajnaPool_, address borrower_)
        external
        view
        returns (
            uint256 kickTime_,
            uint256 collateral_,
            uint256 debtToCover_,
            bool isCollateralized_,
            uint256 price_,
            uint256 neutralPrice_,
            uint256 referencePrice_,
            uint256 debtToCollateral_,
            uint256 bondFactor_
        );

    /**
     *  @notice Returns details of an auction for a given borrower address.
     *  @dev    Calls and returns all values from pool.auctionInfo().
     *  @param  ajnaPool_         Address of `Ajna` pool.
     *  @param  borrower_         Address of the borrower that is liquidated.
     *  @return kicker_           Address of the kicker that is kicking the auction.
     *  @return bondFactor_       The factor used for calculating bond size.
     *  @return bondSize_         The bond amount in quote token terms.
     *  @return kickTime_         Time the liquidation was initiated.
     *  @return referencePrice_   Price used to determine auction start price.
     *  @return neutralPrice_     `Neutral Price` of auction.
     *  @return debtToCollateral_ Borrower debt to collateral at time of kick, which is used in BPF for kicker's reward calculation.
     *  @return head_             Address of the head auction.
     *  @return next_             Address of the next auction in queue.
     *  @return prev_             Address of the prev auction in queue.
     */
    function auctionInfo(address ajnaPool_, address borrower_)
        external
        view
        returns (
            address kicker_,
            uint256 bondFactor_,
            uint256 bondSize_,
            uint256 kickTime_,
            uint256 referencePrice_,
            uint256 neutralPrice_,
            uint256 debtToCollateral_,
            address head_,
            address next_,
            address prev_
        );

    /**
     *  @notice Retrieves info of a given borrower in a given `Ajna` pool.
     *  @param  ajnaPool_         Address of `Ajna` pool.
     *  @param  borrower_         Borrower's address.
     *  @return debt_             Current debt owed by borrower (`WAD`).
     *  @return collateral_       Pledged collateral, including encumbered (`WAD`).
     *  @return t0Np_             `Neutral price` (`WAD`).
     *  @return thresholdPrice_   Borrower's `Threshold Price` (`WAD`).
     */
    function borrowerInfo(address ajnaPool_, address borrower_)
        external
        view
        returns (uint256 debt_, uint256 collateral_, uint256 t0Np_, uint256 thresholdPrice_);

    /**
     *  @notice Get a bucket struct for a given index.
     *  @param  ajnaPool_     Address of `Ajna` pool.
     *  @param  index_        The index of the bucket to retrieve.
     *  @return price_        Bucket's price (`WAD`).
     *  @return quoteTokens_  Amount of quote token in bucket, `deposit + interest` (`WAD`).
     *  @return collateral_   Unencumbered collateral in bucket (`WAD`).
     *  @return bucketLP_     Outstanding `LP` balance in bucket (`WAD`).
     *  @return scale_        Lender interest multiplier (`WAD`).
     *  @return exchangeRate_ The exchange rate of the bucket, in `WAD` units.
     */
    function bucketInfo(address ajnaPool_, uint256 index_)
        external
        view
        returns (
            uint256 price_,
            uint256 quoteTokens_,
            uint256 collateral_,
            uint256 bucketLP_,
            uint256 scale_,
            uint256 exchangeRate_
        );

    /**
     *  @notice Returns info related to pool loans.
     *  @param  ajnaPool_              Address of `Ajna` pool.
     *  @return poolSize_              The total amount of quote tokens in pool (`WAD`).
     *  @return loansCount_            The number of loans in pool.
     *  @return maxBorrower_           The address with the highest `TP` in pool.
     *  @return pendingInflator_       Pending inflator in pool.
     *  @return pendingInterestFactor_ Factor used to scale the inflator.
     */
    function poolLoansInfo(address ajnaPool_)
        external
        view
        returns (
            uint256 poolSize_,
            uint256 loansCount_,
            address maxBorrower_,
            uint256 pendingInflator_,
            uint256 pendingInterestFactor_
        );

    /**
     *  @notice Returns info related to pool prices.
     *  @param  ajnaPool_ Address of `Ajna` pool.
     *  @return hpb_      The price value of the current `Highest Price Bucket` (`HPB`), in `WAD` units.
     *  @return hpbIndex_ The index of the current `Highest Price Bucket` (`HPB`), in `WAD` units.
     *  @return htp_      The price value of the current `Highest Threshold Price` (`HTP`) bucket, in `WAD` units.
     *  @return htpIndex_ The index of the current `Highest Threshold Price` (`HTP`) bucket, in `WAD` units.
     *  @return lup_      The price value of the current `Lowest Utilized Price` (LUP) bucket, in `WAD` units.
     *  @return lupIndex_ The index of the current `Lowest Utilized Price` (`LUP`) bucket, in `WAD` units.
     */
    function poolPricesInfo(address ajnaPool_)
        external
        view
        returns (uint256 hpb_, uint256 hpbIndex_, uint256 htp_, uint256 htpIndex_, uint256 lup_, uint256 lupIndex_);

    /**
     *  @notice Returns the amount of quote token available for borrowing or removing from pool.
     *  @dev    Calculated as the difference between pool balance and escrowed amounts locked in pool (auction bons + unclaimed reserves).
     *  @param  ajnaPool_ Address of `Ajna` pool.
     *  @return amount_   The total quote token amount available to borrow or to be removed from pool, in `WAD` units.
     */
    function availableQuoteTokenAmount(address ajnaPool_) external view returns (uint256 amount_);

    /**
     *  @notice Returns info related to `Claimaible Reserve Auction`.
     *  @param  ajnaPool_                   Address of `Ajna` pool.
     *  @return reserves_                   The amount of excess quote tokens.
     *  @return claimableReserves_          Denominated in quote token, or `0` if no reserves can be auctioned.
     *  @return claimableReservesRemaining_ Amount of claimable reserves which has not yet been taken.
     *  @return auctionPrice_               Current price at which `1` quote token may be purchased, denominated in `Ajna`.
     *  @return timeRemaining_              Seconds remaining before takes are no longer allowed.
     */
    function poolReservesInfo(address ajnaPool_)
        external
        view
        returns (
            uint256 reserves_,
            uint256 claimableReserves_,
            uint256 claimableReservesRemaining_,
            uint256 auctionPrice_,
            uint256 timeRemaining_
        );

    /**
     *  @notice Returns info related to pool utilization.
     *  @param  ajnaPool_              Address of `Ajna` pool.
     *  @return poolMinDebtAmount_     Minimum debt amount.
     *  @return poolCollateralization_ Current pool collateralization ratio.
     *  @return poolActualUtilization_ The current pool actual utilization, in `WAD` units.
     *  @return poolTargetUtilization_ The current pool Target utilization, in `WAD` units.
     */
    function poolUtilizationInfo(address ajnaPool_)
        external
        view
        returns (
            uint256 poolMinDebtAmount_,
            uint256 poolCollateralization_,
            uint256 poolActualUtilization_,
            uint256 poolTargetUtilization_
        );

    /**
     *  @notice Returns the proportion of interest rate which is awarded to lenders;
     *          the remainder accumulates in reserves.
     *  @param  ajnaPool_             Address of `Ajna` pool.
     *  @return lenderInterestMargin_ Lender interest margin in pool.
     */
    function lenderInterestMargin(address ajnaPool_) external view returns (uint256 lenderInterestMargin_);

    /**
     *  @notice Returns bucket price for a given bucket index.
     */
    function indexToPrice(uint256 index_) external pure returns (uint256);

    /**
     *  @notice Returns bucket index for a given bucket price.
     */
    function priceToIndex(uint256 price_) external pure returns (uint256);

    /**
     *  @notice Returns current `LUP` for a given pool.
     */
    function lup(address ajnaPool_) external view returns (uint256);
    /**
     *  @notice Returns current `LUP` index for a given pool.
     */
    function lupIndex(address ajnaPool_) external view returns (uint256);

    /**
     *  @notice Returns current `HPB` for a given pool.
     */
    function hpb(address ajnaPool_) external view returns (uint256);

    /**
     *  @notice Returns current `HPB` index for a given pool.
     */
    function hpbIndex(address ajnaPool_) external view returns (uint256);

    /**
 

Tags:
ERC20, Multisig, Mintable, Burnable, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xb0a19bc124a2c5bc76bf018578a8e582b90774c8|verified:true|block:23671364|tx:0x467d7645ef7005b342bc4ef219362609a4b421085fe159e7b1e51d5b0a9684c6|first_check:1761642493

Submitted on: 2025-10-28 10:08:15

Comments

Log in to comment.

No comments yet.