LVLidoVaultUtil

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/LVLidoVaultUtil.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity ^0.8.20;

import {ILVLidoVault} from "./interfaces/ILVLidoVault.sol";
import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol";
import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol";
import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol";
import {IWsteth} from "./interfaces/vault/IWsteth.sol";
import {VaultLib} from "./libraries/VaultLib.sol";
import {IPoolInfoUtils} from "./interfaces/IPoolInfoUtils.sol";
import {IERC20Pool} from "./interfaces/pool/erc20/IERC20Pool.sol";
import {IERC20} from "@balancer/solidity-utils/openzeppelin/IERC20.sol";
import {IWeth} from "./interfaces/vault/IWeth.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract LVLidoVaultUtil is AutomationCompatibleInterface, Ownable, FunctionsClient {
    using FunctionsRequest for FunctionsRequest.Request;

    ILVLidoVault public LVLidoVault;
    IPoolInfoUtils public constant poolInfoUtils = IPoolInfoUtils(0x30c5eF2997d6a882DE52c4ec01B6D0a5e5B4fAAE);
    uint256 stEthPerToken = IWsteth(address(VaultLib.COLLATERAL_TOKEN)).stEthPerToken();
    AggregatorV3Interface internal stethUsdPriceFeed;
    AggregatorV3Interface internal ethUsdPriceFeed;
    uint8 public constant PRICE_FEED_DECIMALS = 8;
    // Each top-up tranche is triggered after an additional ≈1.1 % market draw-down
    // (1 / leverageFactor when leverageFactor≈15). Three tranches correspond to
    // 1.11 %, 2.22 %, 3.33 % cumulative price moves, after which liquidation may be allowed.
    uint256 public constant FACTOR_COLLATERAL_INCREASE = 11e15; // 1.1 %
    // Exactly three collateral-lender tranches; when the counter reaches 3 we switch to allowKick.
    uint256 public constant MAX_TRANCHES = 3;
    uint256 public constant lidoClaimDelay = 10 days;

    // Rate is set by the LVLidoVault contract
    uint256 public upperBoundRate = 0;
    uint256 public lowerBoundRate = 0;

    // Receipt hash for cryptographic proof verification
    // string public receiptHash;

    // uint256 public rate = 221e14;
    bool public updateRateNeeded = true;
    uint256 public s_lastUpkeepTimeStamp;
    uint256 public s_requestCounter;
    uint64 public s_subscriptionId;
    uint32 public s_fulfillGasLimit;
    bytes32 public s_lastRequestId;
    bytes public s_requestCBOR;
    bytes public s_lastResponse;
    bytes public s_lastError;
    address public s_forwarderAddress;

    constructor(address _LVLidoVault) Ownable(msg.sender) FunctionsClient(VaultLib.router) {
        LVLidoVault = ILVLidoVault(_LVLidoVault);
        stethUsdPriceFeed = AggregatorV3Interface(0xCfE54B5cD566aB89272946F602D76Ea879CAb4a8);
        ethUsdPriceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
        // Set rate bounds: 0.5% to 10% (in 1e18 scale)
        lowerBoundRate = 5e15; // 0.5%
        upperBoundRate = 1e17; // 10%
    }

    modifier onlyForwarder() {
        if (msg.sender != s_forwarderAddress) {
            revert VaultLib.OnlyForwarder();
        }
        _;
    }

    modifier onlyLVLidoVault() {
        if (msg.sender != address(LVLidoVault)) {
            revert VaultLib.Unauthorized();
        }
        _;
    }

    function getWstethToWeth(uint256 _amount) public view returns (uint256) {
        // WSTETH -> STETH -> USD -> ETH -> USD -> WETH
        (, int256 stethPrice,,,) = stethUsdPriceFeed.latestRoundData();
        uint256 stethAmount = _amount * IWsteth(VaultLib.COLLATERAL_TOKEN).stEthPerToken() / 1e18;
        // STETH -> USD
        uint256 stethValueScaled = stethAmount * uint256(stethPrice);
        // USD -> ETH = WETH
        (, int256 ethPrice,,,) = ethUsdPriceFeed.latestRoundData();
        return stethValueScaled / uint256(ethPrice);
    }

    function checkUpkeep(bytes calldata) external view override returns (bool upkeepNeeded, bytes memory performData) {
        // Get the rate for the term if it has passed and auction hasn't happened (Ajna debt 0)
        IERC20Pool pool = LVLidoVault.pool();
        (uint256 debt,,,) = poolInfoUtils.borrowerInfo(address(pool), address(LVLidoVault));
        if (updateRateNeeded && block.timestamp > (LVLidoVault.epochStart() + LVLidoVault.termDuration()) && debt > 0) {
            upkeepNeeded = true;
            performData = abi.encode(221); // Task ID 221: Get new rate
            return (upkeepNeeded, performData);
        }

        // Calculate the market rate rather than the redemption rate

        uint256 newRedemptionRate = getWstethToWeth(1e18);

        int256 percentageDifferenceRedemptionRate = (
            (int256(newRedemptionRate) - int256(LVLidoVault.currentRedemptionRate())) * 1e18
        ) / int256(LVLidoVault.currentRedemptionRate()); // Drawdown percentage

        int256 currentThreshold = LVLidoVault.priceDifferencethreshold()
            - int256(FACTOR_COLLATERAL_INCREASE * LVLidoVault.collateralLenderTraunche()); // -1% - 33% * tranche_num

        if (
            LVLidoVault.epochStarted() && block.timestamp < (LVLidoVault.epochStart() + LVLidoVault.termDuration())
                && debt > 0
        ) {
            uint256 tranchesToTrigger = 0;
            int256 checkThreshold = LVLidoVault.priceDifferencethreshold(); // -1%
            uint256 collateralLenderTraunche = LVLidoVault.collateralLenderTraunche();
            while (
                percentageDifferenceRedemptionRate < checkThreshold
                    && tranchesToTrigger + collateralLenderTraunche < MAX_TRANCHES
            ) {
                tranchesToTrigger++;
                // -1% - 33% * (0 + 1) = -34%
                // -1% - 33% * (0 + 2) = -67%
                // -1% - 33% * (0 + 3) = -100%
                checkThreshold = LVLidoVault.priceDifferencethreshold()
                    - int256(FACTOR_COLLATERAL_INCREASE * (collateralLenderTraunche + tranchesToTrigger));
            }

            uint256 totalCLDepositsUnutilized = LVLidoVault.totalCLDepositsUnutilized();
            if (
                tranchesToTrigger > 0 && tranchesToTrigger + collateralLenderTraunche <= MAX_TRANCHES
                    && totalCLDepositsUnutilized > 0
            ) {
                // Equal-sized tranche approach (Josh): add 1/N of remaining protector funds
                uint256 remainingTranches = MAX_TRANCHES - collateralLenderTraunche;
                if (remainingTranches == 0) remainingTranches = 1;
                uint256 collateralToAddToPreventLiquidation =
                    LVLidoVault.totalCLDepositsUnutilized() / remainingTranches;

                if (collateralToAddToPreventLiquidation > 0) {
                    upkeepNeeded = true;
                    performData = abi.encode(0); // Task ID 0: Add collateral (Avoid Liquidation)
                    return (upkeepNeeded, performData);
                }
            } else if (tranchesToTrigger + collateralLenderTraunche >= MAX_TRANCHES) {
                upkeepNeeded = true;
                performData = abi.encode(3); // Task ID 3: Allow kick
                return (upkeepNeeded, performData);
            }
        } else if (
            LVLidoVault.epochStarted() && block.timestamp > (LVLidoVault.epochStart() + LVLidoVault.termDuration())
                && LVLidoVault.getAllowKick() == false
        ) {
            if (debt == 0) {
                return (true, abi.encode(2)); // Auction happened and debt was cleared, queue task ID 2
            } else if (!LVLidoVault.fundsQueued()) {
                // See if Ajna debt is 0 or not
                (uint256 currentDebt,,,) = poolInfoUtils.borrowerInfo(address(LVLidoVault.pool()), address(LVLidoVault));
                if (currentDebt == 0) {
                    upkeepNeeded = true;
                    performData = abi.encode(2); // Task ID 2: Withdraw funds
                    return (upkeepNeeded, performData);
                }
                upkeepNeeded = true;
                performData = abi.encode(1); // Task ID 1: End term and queue funds
                return (upkeepNeeded, performData);
            } else {
                // Determine how much ETH can be claimed
                uint256 firstIndex = 1;
                uint256 lastIndex = VaultLib.LIDO_WITHDRAWAL.getLastCheckpointIndex();
                uint256[] memory requestIds = new uint256[](1);
                requestIds[0] = LVLidoVault.requestId();
                uint256[] memory hints = VaultLib.LIDO_WITHDRAWAL.findCheckpointHints(requestIds, firstIndex, lastIndex);
                uint256[] memory claimableEthValues = VaultLib.LIDO_WITHDRAWAL.getClaimableEther(requestIds, hints);
                uint256 amount = claimableEthValues[0];

                if (amount > 0) {
                    upkeepNeeded = true;
                    performData = abi.encode(2); // Task ID 2: Withdraw funds
                    return (upkeepNeeded, performData);
                }
            }
        }
        upkeepNeeded = false;
        performData = "";
        return (upkeepNeeded, performData);
    }

    function performUpkeep(bytes calldata performData) external override onlyForwarder {
        if (performData.length == 0) revert VaultLib.InvalidInput();
        uint256 taskId = abi.decode(performData, (uint256));
        IERC20Pool pool = LVLidoVault.pool();
        (uint256 t1Debt,,,) = poolInfoUtils.borrowerInfo(address(pool), address(LVLidoVault));
        if (taskId == 221 && updateRateNeeded && t1Debt > 0) {
            getRate();
            emit VaultLib.TermEnded(LVLidoVault.epochStart() + LVLidoVault.termDuration());
            return;
        }
        (uint256 t0Debt, uint256 collateral,) = pool.borrowerInfo(address(LVLidoVault));

        // Add collateral to Ajna pool; Logic for Avoid Liquidations
        // Only proceed if we're in an active epoch
        if (
            LVLidoVault.epochStarted() && block.timestamp < (LVLidoVault.epochStart() + LVLidoVault.termDuration())
                && t1Debt > 0
        ) {
            uint256 newRedemptionRate = getWstethToWeth(1e18);

            // Calculate price change as percentage
            int256 percentageDifferenceRedemptionRate = (
                (int256(newRedemptionRate) - int256(LVLidoVault.currentRedemptionRate())) * 1e18
            ) / int256(LVLidoVault.currentRedemptionRate());

            // Calculate how many tranches should be triggered
            uint256 tranchesToTrigger = 0;
            int256 checkThreshold = LVLidoVault.priceDifferencethreshold();

            // Count how many thresholds have been crossed
            // -20% < -1% && 0 +
            while (
                percentageDifferenceRedemptionRate < checkThreshold
                    && tranchesToTrigger + LVLidoVault.collateralLenderTraunche() < MAX_TRANCHES
            ) {
                tranchesToTrigger++;
                checkThreshold = LVLidoVault.priceDifferencethreshold()
                    - int256(FACTOR_COLLATERAL_INCREASE * (LVLidoVault.collateralLenderTraunche() + tranchesToTrigger));
            }
            if (
                tranchesToTrigger > 0 && tranchesToTrigger + LVLidoVault.collateralLenderTraunche() <= MAX_TRANCHES
                    && LVLidoVault.totalCLDepositsUnutilized() > 0
            ) {
                // Equal-sized tranche approach (Josh): add 1/N of remaining protector funds
                uint256 remainingTranches = MAX_TRANCHES - LVLidoVault.collateralLenderTraunche();
                if (remainingTranches == 0) remainingTranches = 1;
                uint256 collateralToAddToPreventLiquidation =
                    LVLidoVault.totalCLDepositsUnutilized() / remainingTranches;

                if (collateralToAddToPreventLiquidation > 0) {
                    LVLidoVault.avoidLiquidation(collateralToAddToPreventLiquidation);
                    LVLidoVault.setCollateralLenderTraunche(LVLidoVault.collateralLenderTraunche() + tranchesToTrigger);
                    LVLidoVault.setCurrentRedemptionRate(newRedemptionRate);
                }
            } else if (tranchesToTrigger + LVLidoVault.collateralLenderTraunche() >= MAX_TRANCHES) {
                LVLidoVault.setAllowKick(true);
            }
        }
        // Request withdrawals
        else if (
            LVLidoVault.epochStarted() && block.timestamp > (LVLidoVault.epochStart() + LVLidoVault.termDuration())
                && !updateRateNeeded && LVLidoVault.getAllowKick() == false
        ) {
            if (taskId == 1 && t1Debt > 0) {
                uint256 approxPercentFinalInterest =
                    (LVLidoVault.rate() * ((block.timestamp - LVLidoVault.epochStart()) + lidoClaimDelay)) / 365 days;
                uint256 stethPerWsteth = getWstethToWeth(1e18);
                LVLidoVault.setCurrentRedemptionRate(stethPerWsteth);
                emit VaultLib.RedemptionRateUpdated(stethPerWsteth);
                uint256 approxCTForClaim =
                    (LVLidoVault.totalBorrowAmount() * (1e18 + uint256(approxPercentFinalInterest))) / stethPerWsteth;
                require(
                    LVLidoVault.approveForProxy(
                        VaultLib.COLLATERAL_TOKEN, address(VaultLib.LIDO_WITHDRAWAL), approxCTForClaim
                    ),
                    "Approval failure."
                );
                // Todo: Debt exceeds borrower leveraged collateral + epoch collateralLender funds (utilized + unutilized)
                uint256[] memory amounts = new uint256[](1);
                amounts[0] = approxCTForClaim;
                uint256 _requestId = LVLidoVault.requestWithdrawalsWstETH(amounts);
                emit VaultLib.FundsQueued(_requestId, approxCTForClaim);
            }
            // Withdraw funds, End epoch
            else if (taskId == 2) {
                // Determine actual debt
                uint256 timeElapsed = block.timestamp - LVLidoVault.epochStart();
                uint256 actualDebt;
                if (t1Debt > 0) {
                    actualDebt = (LVLidoVault.totalBorrowAmount() * (1e18 + ((LVLidoVault.rate() * timeElapsed) / 365 days))) / 1e18;
                } else {
                    actualDebt = 0;
                }
                uint256 claimAmount = 0;
                if (LVLidoVault.fundsQueued()) {
                    uint256 firstIndex = 1;
                    uint256 lastIndex = VaultLib.LIDO_WITHDRAWAL.getLastCheckpointIndex();
                    uint256[] memory requestIds = new uint256[](1);
                    requestIds[0] = LVLidoVault.requestId();
                    uint256[] memory hints =
                        VaultLib.LIDO_WITHDRAWAL.findCheckpointHints(requestIds, firstIndex, lastIndex);
                    uint256[] memory claimableEthValues = VaultLib.LIDO_WITHDRAWAL.getClaimableEther(requestIds, hints);
                    claimAmount = claimableEthValues[0];

                    if (claimAmount > 0) {
                        // Claim if claim exists
                        LVLidoVault.claimWithdrawal();
                        emit VaultLib.FundsClaimed(LVLidoVault.requestId(), claimAmount);
                        LVLidoVault.depositEthForWeth(claimAmount); // ETH -> WETH
                    } else {
                        // Revert if Ajna debt is greater than 0
                        if (t1Debt != 0) {
                            revert VaultLib.NoETHToClaim();
                        }
                        // Otherwise, LVLidoVault.repayAjnaDebt allows us to continue
                    }
                }

                uint256 claimAmountWstethRemaining = 0;
                uint256 matchedLendersOwed;
                // Is our claim amount enough for the debt according LVLidoVault.rate()?

                if (actualDebt > claimAmount + LVLidoVault.totalManualRepay()) {
                    revert("Debt greater than available funds");
                } else {
                    // Claim amount larger than actual debt
                    if (actualDebt < claimAmount) {
                        claimAmountWstethRemaining = LVLidoVault.wethToWsteth(claimAmount - actualDebt);
                    }
                    // Actual debt smaller than claimAmount + totalManualRepay
                    else {
                        LVLidoVault.setTotalManualRepay(LVLidoVault.totalManualRepay() - (actualDebt - claimAmount));
                    }
                    claimAmount = 0;
                }
                if (actualDebt > 0) {
                    require(
                        LVLidoVault.mintForProxy(address(LVLidoVault.testQuoteToken()), address(LVLidoVault), t1Debt)
                            && LVLidoVault.approveForProxy(address(LVLidoVault.testQuoteToken()), address(pool), t1Debt),
                        "Upkeep failure."
                    );
                    matchedLendersOwed =
                        LVLidoVault.totalLenderQTUtilized() - LVLidoVault.totalBorrowAmount() + actualDebt;
                } else {
                    // Auction happened, so we use the balance to account for debt repaid
                    matchedLendersOwed = IERC20(VaultLib.QUOTE_TOKEN).balanceOf(address(LVLidoVault))
                        - LVLidoVault.totalLenderQTUnutilized();
                }

                if (t1Debt > 0 || collateral > 0) {
                    LVLidoVault.repayDebtForProxy(t1Debt, collateral);
                }

                // Hardcoded 0.5% APY for epoch collateral lenders
                uint256 matchedCollateralLendersOwed = (
                    (LVLidoVault.totalCLDepositsUnutilized() + LVLidoVault.totalCLDepositsUtilized())
                        * (1e18 + ((timeElapsed * 5e15) / 365 days))
                ) / 1e18;
                uint256 totalEpochCollateral = IERC20(VaultLib.COLLATERAL_TOKEN).balanceOf(address(LVLidoVault))
                    - LVLidoVault.totalBorrowerCTUnutilized() - LVLidoVault.totalCollateralLenderCT();
                if (matchedCollateralLendersOwed > totalEpochCollateral) {
                    matchedCollateralLendersOwed = totalEpochCollateral;
                    totalEpochCollateral = 0;
                } else {
                    totalEpochCollateral -= matchedCollateralLendersOwed;
                }

                uint256 matchedBorrowersOwed = totalEpochCollateral;
                emit VaultLib.AmountsOwed(matchedLendersOwed, matchedBorrowersOwed, matchedCollateralLendersOwed);

                uint256 depositSize = pool.depositSize();
                LVLidoVault.clearAjnaDeposits(depositSize);
                require(
                    LVLidoVault.burnForProxy(
                        address(LVLidoVault.testCollateralToken()),
                        address(LVLidoVault),
                        LVLidoVault.testCollateralToken().balanceOf(address(LVLidoVault))
                    )
                        && LVLidoVault.burnForProxy(
                            address(LVLidoVault.testQuoteToken()),
                            address(LVLidoVault),
                            IERC20(address(LVLidoVault.testQuoteToken())).balanceOf(address(LVLidoVault))
                        ),
                    "Upkeep failure."
                );

                // Scale each amount based on remaining balances, remove match, and add back to lenderOrders and borrowerOrders
                uint256 totalLenderQTUtilizedToRemove;
                uint256 totalLenderQTUnutilizedToAdjust;
                uint256 newTotalBorrowerCT = LVLidoVault.totalBorrowerCT();
                uint256 newTotalBorrowerCTUnutilized = LVLidoVault.totalBorrowerCTUnutilized();

                for (uint256 i = 0; i < LVLidoVault.getEpochMatches(LVLidoVault.epoch()).length; i++) {
                    VaultLib.MatchInfo memory match_ = LVLidoVault.getEpochMatches(LVLidoVault.epoch())[i];
                    // After earning the profit (or worstcase a loss), this will be the new lender quote amount
                    uint256 newLenderQuoteAmount = (
                        (match_.quoteAmount + match_.reservedQuoteAmount) * matchedLendersOwed
                    ) / LVLidoVault.totalLenderQTUtilized();
                    uint256 newBorrowerCTAmount = (match_.collateralAmount * matchedBorrowersOwed)
                    // After earning the profit (or worstcase a loss), this will be the new borrower collateral amount
                    / (LVLidoVault.totalBorrowerCT() - LVLidoVault.totalBorrowerCTUnutilized());
                    LVLidoVault.lenderOrdersPush(VaultLib.LenderOrder(match_.lender, newLenderQuoteAmount, 0));
                    LVLidoVault.borrowerOrdersPush(VaultLib.BorrowerOrder(match_.borrower, newBorrowerCTAmount));
                    totalLenderQTUtilizedToRemove += match_.quoteAmount + match_.reservedQuoteAmount;
                    totalLenderQTUnutilizedToAdjust += newLenderQuoteAmount;
                    newTotalBorrowerCTUnutilized += newBorrowerCTAmount;
                    newTotalBorrowerCT = newTotalBorrowerCT - match_.collateralAmount + newBorrowerCTAmount;
                }
                emit VaultLib.EpochInterestEarned(
                    LVLidoVault.epoch(),
                    (LVLidoVault.totalLenderQTUnutilized() + totalLenderQTUnutilizedToAdjust)
                        > LVLidoVault.totalLenderQTUtilized()
                        ? (LVLidoVault.totalLenderQTUnutilized() + totalLenderQTUnutilizedToAdjust)
                            - LVLidoVault.totalLenderQTUtilized()
                        : 0,
                    newTotalBorrowerCT > LVLidoVault.totalBorrowerCT()
                        ? newTotalBorrowerCT - LVLidoVault.totalBorrowerCT()
                        : 0,
                    matchedCollateralLendersOwed
                        > (LVLidoVault.totalCLDepositsUnutilized() + LVLidoVault.totalCLDepositsUtilized())
                        ? matchedCollateralLendersOwed
                            - (LVLidoVault.totalCLDepositsUnutilized() + LVLidoVault.totalCLDepositsUtilized())
                        : 0
                );
                LVLidoVault.setTotalLenderQTUnutilized(
                    LVLidoVault.totalLenderQTUnutilized() + totalLenderQTUnutilizedToAdjust
                );
                LVLidoVault.setTotalLenderQTUtilized(
                    0 // LVLidoVault.totalLenderQTUtilized() - totalLenderQTUtilizedToRemove
                ); // Should be 0

                LVLidoVault.setTotalBorrowerCT(newTotalBorrowerCT);
                LVLidoVault.setTotalBorrowerCTUnutilized(newTotalBorrowerCTUnutilized);

                // Erase matches
                LVLidoVault.deleteEpochMatches(LVLidoVault.epoch());
                for (uint256 i = 0; i < LVLidoVault.getEpochCollateralLenderOrders(LVLidoVault.epoch()).length; i++) {
                    VaultLib.CollateralLenderOrder memory collateralLenderOrder_ =
                        LVLidoVault.getEpochCollateralLenderOrders(LVLidoVault.epoch())[i];
                    uint256 newCLCollateralAmount = (
                        collateralLenderOrder_.collateralAmount * matchedCollateralLendersOwed
                    ) / (LVLidoVault.totalCLDepositsUnutilized() + LVLidoVault.totalCLDepositsUtilized());
                    LVLidoVault.collateralLenderOrdersPush(
                        VaultLib.CollateralLenderOrder(collateralLenderOrder_.collateralLender, newCLCollateralAmount)
                    );
                    LVLidoVault.setTotalCollateralLenderCT(
                        LVLidoVault.totalCollateralLenderCT() + newCLCollateralAmount
                    );
                }

                LVLidoVault.deleteEpochCollateralLenderOrders(LVLidoVault.epoch());
                LVLidoVault.setTotalCLDepositsUnutilized(0);
                LVLidoVault.setTotalCLDepositsUtilized(0);

                LVLidoVault.end_epoch();
                LVLidoVault.setAllowKick(false);
                updateRateNeeded = true;
            }
        }
    }

    // CHAINLINK FUNCTIONS
    /**
     * @notice Sets the forwarder address for meta-transactions.
     * @dev Can only be called by the owner.
     * @param forwarderAddress The new forwarder address.
     */
    function setForwarderAddress(address forwarderAddress) public {
        require(msg.sender == LVLidoVault.owner(), "Only callable by LVLidoVault");
        if (forwarderAddress == address(0)) revert VaultLib.InvalidInput(); // Zero address check for security
        emit VaultLib.ForwarderAddressUpdated(s_forwarderAddress, forwarderAddress);
        s_forwarderAddress = forwarderAddress;
    }
    /**
     * @notice Sets the bytes representing the CBOR-encoded FunctionsRequest.Request that is sent when performUpkeep is called
     *
     * @param _subscriptionId The Functions billing subscription ID used to pay for Functions requests
     * @param _fulfillGasLimit Maximum amount of gas used to call the client contract's `handleOracleFulfillment` function
     * @param requestCBOR Bytes representing the CBOR-encoded FunctionsRequest.Request
     */

    function setRequest(bytes memory requestCBOR, uint64 _subscriptionId, uint32 _fulfillGasLimit) external {
        require(msg.sender == LVLidoVault.owner(), "Only callable by LVLidoVault");
        s_subscriptionId = _subscriptionId;
        s_fulfillGasLimit = _fulfillGasLimit;
        s_requestCBOR = requestCBOR;
    }

    /**
     * @notice Sends a request to Chainlink Functions to fetch and compute the new rate
     * @dev This function attempts to send a request using the router contract and handles any errors
     * that may occur during the request.
     */
    function getRate() internal {
        // Update state first
        s_requestCounter = s_requestCounter + 1;

        try i_router.sendRequest(
            s_subscriptionId, s_requestCBOR, FunctionsRequest.REQUEST_DATA_VERSION, s_fulfillGasLimit, VaultLib.donId
        ) returns (bytes32 requestId_) {
            s_lastRequestId = requestId_;
            emit RequestSent(requestId_);
        } catch Error(string memory reason) {
            emit VaultLib.RequestRevertedWithErrorMsg(reason);
            LVLidoVault.updateRate(0);
        } catch (bytes memory data) {
            emit VaultLib.RequestRevertedWithoutErrorMsg(data);
            LVLidoVault.updateRate(0);
        }
    }

    /**
     * @notice Processes Chainlink Functions response
     * @dev Decodes pre-aggregated rate sums (1e27) and calculates average APR (1e18)
     * @param requestId Chainlink request ID
     * @param response Encoded (sumLiquidityRates, sumBorrowRates, numRates)
     * @param err Error data if any
     */
    function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
        // Store request metadata for tracking and debugging purposes
        s_lastRequestId = requestId;
        s_lastResponse = response;
        s_lastError = err;
        s_lastUpkeepTimeStamp = block.timestamp;

        // If there's an error in the response, log it and exit early
        if (err.length > 0) {
            emit VaultLib.OCRResponse(requestId, response, LVLidoVault.rate(), err);
            LVLidoVault.updateRate(0);
            return;
        }

        // Validate response data exists
        if (response.length == 0) {
            emit VaultLib.OCRResponse(requestId, response, LVLidoVault.rate(), abi.encodePacked("Empty response"));
            LVLidoVault.updateRate(0);
            return;
        }

        // Decode the aggregated rate data from Chainlink Functions
        (uint256 sumLiquidityRates_1e27, uint256 sumVariableBorrowRates_1e27, uint256 numRates) =
            abi.decode(response, (uint256, uint256, uint256));
            // receiptHash = hash;
        // Ensure we have valid rate data to process
        if (numRates == 0) {
            emit VaultLib.OCRResponse(requestId, response, LVLidoVault.rate(), abi.encodePacked("Decoded response is empty"));
            return;
        }

        // Calculate the average APR by:
        // 1. Adding supply and borrow rates
        // 2. Dividing by 2 to get the average between supply and borrow
        // 3. Dividing by numRates to get the average across all protocols
        // 4. Converting from 1e27 to 1e18 scale by dividing by 1e9
        uint256 rate = (sumLiquidityRates_1e27 + sumVariableBorrowRates_1e27) / (2 * numRates * 1e9);
        
        // Validate rate is within bounds before updating
        if (rate < lowerBoundRate || rate > upperBoundRate) {
            emit VaultLib.OCRResponse(requestId, response, LVLidoVault.rate(), abi.encodePacked("Rate out of bounds"));
            // Don't update the rate if it's outside bounds
            LVLidoVault.updateRate(0);
            return;
        }
        // Rate is within bounds, proceed with update
        LVLidoVault.updateRate(rate);
        updateRateNeeded = false; // Mark that we've successfully updated the rate
        emit VaultLib.OCRResponse(requestId, response, rate, err);
    }
}
"
    },
    "src/interfaces/ILVLidoVault.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity ^0.8.20;

import {IERC20Pool} from "./pool/erc20/IERC20Pool.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {VaultLib} from "../libraries/VaultLib.sol";

interface ILVLidoVault {
    function pool() external view returns (IERC20Pool);
    function totalBorrowAmount() external view returns (uint256);
    function totalCLDepositsUnutilized() external view returns (uint256);
    function totalCLDepositsUtilized() external view returns (uint256);
    function epochStartRedemptionRate() external view returns (uint256);
    function epochStart() external view returns (uint256);
    function termDuration() external view returns (uint256);
    function currentRedemptionRate() external view returns (uint256);
    function priceDifferencethreshold() external view returns (int256);
    function collateralLenderTraunche() external view returns (uint256);
    function epochStarted() external view returns (bool);
    function getInverseInitialCollateralLenderDebtRatio() external view returns (uint256);
    function fundsQueued() external view returns (bool);
    function requestId() external view returns (uint256);
    function getRate() external;
    function avoidLiquidation(uint256 collateralToAdd) external;
    function setCollateralLenderTraunche(uint256 newTraunche) external;
    function setCurrentRedemptionRate(uint256 newRate) external;
    function setAllowKick(bool allow) external;
    function rate() external view returns (uint256);
    function approveForProxy(address token, address spender, uint256 amount) external returns (bool);
    function requestWithdrawalsWstETH(uint256[] calldata amounts) external returns (uint256);
    function claimWithdrawal() external;
    function depositEthForWeth(uint256 amount) external;
    function totalManualRepay() external view returns (uint256);
    function wethToWsteth(uint256 amount) external returns (uint256);
    function setTotalManualRepay(uint256 newTotal) external;
    function testQuoteToken() external view returns (address);
    function mintForProxy(address token, address to, uint256 amount) external returns (bool);
    function totalLenderQTUtilized() external view returns (uint256);
    function totalLenderQTUnutilized() external view returns (uint256);
    function repayDebtForProxy(uint256 debtAmount, uint256 collateralAmount) external;
    function totalBorrowerCTUnutilized() external view returns (uint256);
    function totalCollateralLenderCT() external view returns (uint256);
    function clearAjnaDeposits(uint256 depositSize) external;
    function testCollateralToken() external view returns (IERC20);
    function burnForProxy(address token, address from, uint256 amount) external returns (bool);
    function totalBorrowerCT() external view returns (uint256);
    function epoch() external view returns (uint256);
    function getEpochMatches(uint256 epoch) external view returns (VaultLib.MatchInfo[] memory);
    function lenderOrdersPush(VaultLib.LenderOrder memory order) external;
    function borrowerOrdersPush(VaultLib.BorrowerOrder memory order) external;
    function setTotalLenderQTUnutilized(uint256 amount) external;
    function setTotalLenderQTUtilized(uint256 amount) external;
    function setTotalBorrowerCT(uint256 amount) external;
    function setTotalBorrowerCTUnutilized(uint256 amount) external;
    function deleteEpochMatches(uint256 epoch) external;
    function getEpochCollateralLenderOrders(uint256 epoch)
        external
        view
        returns (VaultLib.CollateralLenderOrder[] memory);
    function collateralLenderOrdersPush(VaultLib.CollateralLenderOrder memory order) external;
    function setTotalCollateralLenderCT(uint256 amount) external;
    function deleteEpochCollateralLenderOrders(uint256 epoch) external;
    function setTotalCLDepositsUnutilized(uint256 amount) external;
    function setTotalCLDepositsUtilized(uint256 amount) external;
    function end_epoch() external;
    function owner() external view returns (address);
    function getAllowKick() external view returns (bool);
    function updateRate(uint256 _rate) external;
}
"
    },
    "lib/chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AutomationBase} from "./AutomationBase.sol";
import {AutomationCompatibleInterface} from "./interfaces/AutomationCompatibleInterface.sol";

abstract contract AutomationCompatible is AutomationBase, AutomationCompatibleInterface {}
"
    },
    "lib/chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IFunctionsRouter} from "./interfaces/IFunctionsRouter.sol";
import {IFunctionsClient} from "./interfaces/IFunctionsClient.sol";

import {FunctionsRequest} from "./libraries/FunctionsRequest.sol";

/// @title The Chainlink Functions client contract
/// @notice Contract developers can inherit this contract in order to make Chainlink Functions requests
abstract contract FunctionsClient is IFunctionsClient {
  using FunctionsRequest for FunctionsRequest.Request;

  IFunctionsRouter internal immutable i_router;

  event RequestSent(bytes32 indexed id);
  event RequestFulfilled(bytes32 indexed id);

  error OnlyRouterCanFulfill();

  constructor(address router) {
    i_router = IFunctionsRouter(router);
  }

  /// @notice Sends a Chainlink Functions request
  /// @param data The CBOR encoded bytes data for a Functions request
  /// @param subscriptionId The subscription ID that will be charged to service the request
  /// @param callbackGasLimit the amount of gas that will be available for the fulfillment callback
  /// @return requestId The generated request ID for this request
  function _sendRequest(
    bytes memory data,
    uint64 subscriptionId,
    uint32 callbackGasLimit,
    bytes32 donId
  ) internal returns (bytes32) {
    bytes32 requestId = i_router.sendRequest(
      subscriptionId,
      data,
      FunctionsRequest.REQUEST_DATA_VERSION,
      callbackGasLimit,
      donId
    );
    emit RequestSent(requestId);
    return requestId;
  }

  /// @notice User defined function to handle a response from the DON
  /// @param requestId The request ID, returned by sendRequest()
  /// @param response Aggregated response from the execution of the user's source code
  /// @param err Aggregated error from the execution of the user code or from the execution pipeline
  /// @dev Either response or error parameter will be set, but never both
  function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal virtual;

  /// @inheritdoc IFunctionsClient
  function handleOracleFulfillment(bytes32 requestId, bytes memory response, bytes memory err) external override {
    if (msg.sender != address(i_router)) {
      revert OnlyRouterCanFulfill();
    }
    fulfillRequest(requestId, response, err);
    emit RequestFulfilled(requestId);
  }
}
"
    },
    "lib/chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {CBOR} from "../../../vendor/solidity-cborutils/v2.0.0/CBOR.sol";

/// @title Library for encoding the input data of a Functions request into CBOR
library FunctionsRequest {
  using CBOR for CBOR.CBORBuffer;

  uint16 public constant REQUEST_DATA_VERSION = 1;
  uint256 internal constant DEFAULT_BUFFER_SIZE = 256;

  enum Location {
    Inline, // Provided within the Request
    Remote, // Hosted through remote location that can be accessed through a provided URL
    DONHosted // Hosted on the DON's storage
  }

  enum CodeLanguage {
    JavaScript
    // In future version we may add other languages
  }

  struct Request {
    Location codeLocation; // ════════════╸ The location of the source code that will be executed on each node in the DON
    Location secretsLocation; // ═════════╸ The location of secrets that will be passed into the source code. *Only Remote secrets are supported
    CodeLanguage language; // ════════════╸ The coding language that the source code is written in
    string source; // ════════════════════╸ Raw source code for Request.codeLocation of Location.Inline, URL for Request.codeLocation of Location.Remote, or slot decimal number for Request.codeLocation of Location.DONHosted
    bytes encryptedSecretsReference; // ══╸ Encrypted URLs for Request.secretsLocation of Location.Remote (use addSecretsReference()), or CBOR encoded slotid+version for Request.secretsLocation of Location.DONHosted (use addDONHostedSecrets())
    string[] args; // ════════════════════╸ String arguments that will be passed into the source code
    bytes[] bytesArgs; // ════════════════╸ Bytes arguments that will be passed into the source code
  }

  error EmptySource();
  error EmptySecrets();
  error EmptyArgs();
  error NoInlineSecrets();

  /// @notice Encodes a Request to CBOR encoded bytes
  /// @param self The request to encode
  /// @return CBOR encoded bytes
  function encodeCBOR(Request memory self) internal pure returns (bytes memory) {
    CBOR.CBORBuffer memory buffer = CBOR.create(DEFAULT_BUFFER_SIZE);

    buffer.writeString("codeLocation");
    buffer.writeUInt256(uint256(self.codeLocation));

    buffer.writeString("language");
    buffer.writeUInt256(uint256(self.language));

    buffer.writeString("source");
    buffer.writeString(self.source);

    if (self.args.length > 0) {
      buffer.writeString("args");
      buffer.startArray();
      for (uint256 i = 0; i < self.args.length; ++i) {
        buffer.writeString(self.args[i]);
      }
      buffer.endSequence();
    }

    if (self.encryptedSecretsReference.length > 0) {
      if (self.secretsLocation == Location.Inline) {
        revert NoInlineSecrets();
      }
      buffer.writeString("secretsLocation");
      buffer.writeUInt256(uint256(self.secretsLocation));
      buffer.writeString("secrets");
      buffer.writeBytes(self.encryptedSecretsReference);
    }

    if (self.bytesArgs.length > 0) {
      buffer.writeString("bytesArgs");
      buffer.startArray();
      for (uint256 i = 0; i < self.bytesArgs.length; ++i) {
        buffer.writeBytes(self.bytesArgs[i]);
      }
      buffer.endSequence();
    }

    return buffer.buf.buf;
  }

  /// @notice Initializes a Chainlink Functions Request
  /// @dev Sets the codeLocation and code on the request
  /// @param self The uninitialized request
  /// @param codeLocation The user provided source code location
  /// @param language The programming language of the user code
  /// @param source The user provided source code or a url
  function initializeRequest(
    Request memory self,
    Location codeLocation,
    CodeLanguage language,
    string memory source
  ) internal pure {
    if (bytes(source).length == 0) revert EmptySource();

    self.codeLocation = codeLocation;
    self.language = language;
    self.source = source;
  }

  /// @notice Initializes a Chainlink Functions Request
  /// @dev Simplified version of initializeRequest for PoC
  /// @param self The uninitialized request
  /// @param javaScriptSource The user provided JS code (must not be empty)
  function initializeRequestForInlineJavaScript(Request memory self, string memory javaScriptSource) internal pure {
    initializeRequest(self, Location.Inline, CodeLanguage.JavaScript, javaScriptSource);
  }

  /// @notice Adds Remote user encrypted secrets to a Request
  /// @param self The initialized request
  /// @param encryptedSecretsReference Encrypted comma-separated string of URLs pointing to off-chain secrets
  function addSecretsReference(Request memory self, bytes memory encryptedSecretsReference) internal pure {
    if (encryptedSecretsReference.length == 0) revert EmptySecrets();

    self.secretsLocation = Location.Remote;
    self.encryptedSecretsReference = encryptedSecretsReference;
  }

  /// @notice Adds DON-hosted secrets reference to a Request
  /// @param self The initialized request
  /// @param slotID Slot ID of the user's secrets hosted on DON
  /// @param version User data version (for the slotID)
  function addDONHostedSecrets(Request memory self, uint8 slotID, uint64 version) internal pure {
    CBOR.CBORBuffer memory buffer = CBOR.create(DEFAULT_BUFFER_SIZE);

    buffer.writeString("slotID");
    buffer.writeUInt64(slotID);
    buffer.writeString("version");
    buffer.writeUInt64(version);

    self.secretsLocation = Location.DONHosted;
    self.encryptedSecretsReference = buffer.buf.buf;
  }

  /// @notice Sets args for the user run function
  /// @param self The initialized request
  /// @param args The array of string args (must not be empty)
  function setArgs(Request memory self, string[] memory args) internal pure {
    if (args.length == 0) revert EmptyArgs();

    self.args = args;
  }

  /// @notice Sets bytes args for the user run function
  /// @param self The initialized request
  /// @param args The array of bytes args (must not be empty)
  function setBytesArgs(Request memory self, bytes[] memory args) internal pure {
    if (args.length == 0) revert EmptyArgs();

    self.bytesArgs = args;
  }
}
"
    },
    "src/interfaces/vault/IWsteth.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity ^0.8.20;

interface IWsteth {
    /**
     * @notice Returns the amount of stETH that corresponds to one wstETH.
     * @dev The value increases over time as staking rewards accumulate.
     * @return The amount of stETH per one wstETH, scaled to 18 decimals.
     */
    function stEthPerToken() external view returns (uint256);

    /**
     * @notice Returns the amount of wstETH that corresponds to one stETH.
     * @dev This value decreases over time as staking rewards accumulate.
     * @return The amount of wstETH per one stETH, scaled to 18 decimals.
     */
    function tokensPerStEth() external view returns (uint256);

    /**
     * @notice Wraps stETH into wstETH.
     * @dev Transfers `stETH` from the caller and mints `wstETH` to the caller.
     * @param _stETHAmount The amount of stETH to wrap.
     * @return The amount of wstETH minted.
     */
    function wrap(uint256 _stETHAmount) external returns (uint256);
}
"
    },
    "src/libraries/VaultLib.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity ^0.8.20;

import {ILidoWithdrawal} from "../interfaces/vault/ILidoWithdrawal.sol";

/**
 * @title VaultLib
 */
library VaultLib {
    struct LenderOrder {
        address lender;
        uint256 quoteAmount;
        uint256 vaultShares;
    }

    struct BorrowerOrder {
        address borrower;
        uint256 collateralAmount;
    }

    struct CollateralLenderOrder {
        address collateralLender;
        uint256 collateralAmount;
    }

    struct MatchInfo {
        address lender;
        address borrower;
        uint256 quoteAmount; // 97%
        uint256 collateralAmount;
        uint256 reservedQuoteAmount; // 3%
    }

    address public constant QUOTE_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant COLLATERAL_TOKEN = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
    address public constant STETH_ADDRESS = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
    ILidoWithdrawal public constant LIDO_WITHDRAWAL = ILidoWithdrawal(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1);
    address public constant router = 0x65Dcc24F8ff9e51F10DCc7Ed1e4e2A61e6E14bd6;
    bytes32 public constant donId = 0x66756e2d657468657265756d2d6d61696e6e65742d3100000000000000000000;
    uint256 public constant leverageFactor = 150;

    /* EVENTS */
    event EndEpoch(uint256 time);
    event TermEnded(uint256 time);
    event FundsQueued(uint256 requestId, uint256 collateralAmount);
    event FundsClaimed(uint256 requestId, uint256 amount);
    event FundsAdded(uint256 ethAmount, uint256 contractBalance, address sender);
    event ForwarderAddressUpdated(address oldAddress, address newAddress);
    event AvoidLiquidation(uint256 collateralAmount);
    event EpochStarted(uint256 epoch, uint256 epochStart, uint256 termEnd);
    event RedemptionRateUpdated(uint256 redemptionRate);
    event OCRResponse(bytes32 indexed requestId, bytes response, uint256 rate, bytes err);
    event RequestRevertedWithErrorMsg(string reason);
    event RequestRevertedWithoutErrorMsg(bytes data);
    event LVLidoVaultUtilAddressUpdated(address oldAddress, address newAddress);
    event LoanComposition(
        uint256 baseCollateral, uint256 leveragedCollateral, uint256 totalCollateral, uint256 quoteToBorrow
    );
    event AmountsOwed(uint256 lendersOwed, uint256 borrowersOwed, uint256 collateralLendersOwed);
    // ------------------------------------------------------------
    // Confirmed to be used
    event LenderOrderAdded(address lender, uint256 quoteAmount);
    event BorrowerOrderAdded(address borrower, uint256 collateralAmount);
    event CollateralLenderDeposit(address collateralLender, uint256 collateralAmount);
    event WithdrawLender(address lender, uint256 quoteAmount);
    event WithdrawBorrower(address borrower, uint256 collateralAmount);
    event WithdrawCollateralLender(address collateralLender, uint256 collateralAmount);
    event EpochInterestEarned(
        uint256 epochNumber,
        uint256 lendersInterestAccrued,
        uint256 borrowersInterestAccrued,
        uint256 collateralLendersInterestAccrued
    );

    /* Errors */
    error InsufficientFunds();
    error Unauthorized();
    error OnlyForwarder();
    error OnlyProxy();
    error ReentrantCall();
    error InvalidInput();
    error NoETHToClaim();
    error NoUnfilledOrdersFound();
    error MaxFundsExceeded();
    error TokenOperationFailed();
    error LockedBonds();

    // ------------------------------------------------------------
}
"
    },
    "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);

    /**
     *  @notice Returns current `HTP` for a given pool.
     */
    function htp(address ajnaPool_) external view returns (uint256 htp_);
    /**
     *  @notice Calculates origination fee rate for a pool.
     *  @notice Calculated as greater of the current annualized interest rate divided by `52` (one week of interest) or `5` bps.
     *  @return Fee rate calculated from the pool interest rate.
     */
    function borrowFeeRate(address ajnaPool_) external view returns (uint256);
    /**
     *  @notice Calculates deposit fee rate for a pool.
     *  @notice Calculated as current annualized rate divided by 365 * 3 (8 hours of interest)
     *  @return Fee rate calculated from the pool interest rate.
     */
    function depositFeeRate(address ajnaPool_) external view returns (uint256);

    /**
     *  @notice Calculate the amount of quote tokens in bucket for a given amount of `LP`.
     *  @param  lp_          The number of `LP` to calculate amounts for.
     *  @param  index_       The price bucket index for which the value should be calculated.
     *  @return quoteAmount_ The exact amount of quote tokens that can be exchanged for the given `LP`, `WAD` units.
     */
    function lpToQuoteTokens(address ajnaPool_, uint256 lp_, uint256 index_)
        external
        view
        returns (uint256 quoteAmount_);

    /**
     *  @notice Calculate the amount of collateral tokens in bucket for a given amount of `LP`.
     *  @param  lp_               The number of `LP` to calculate amounts for.
     *  @param  index_            The price bucket index for which the value should be calculated.
     *  @return collateralAmount_ The exact amount of collateral tokens that can be exchanged for the given `LP`, `WAD` units.
     */
    function lpToCollateral(address ajnaPool_, uint256 lp_, uint256 index_)
        external
        view
        returns (uint256 collateralAmount_);
}
// /**********************/
// /*** Pool Utilities ***/
// /**********************/

// /**
//  *  @notice Calculates encumberance for a debt amount at a given price.
//  *  @param  debt_         The debt amount to calculate encumberance for.
//  *  @param  price_        The price to calculate encumberance at.
//  *  @return encumberance_ Encumberance value.
//  */
// function _encumberance(
//     uint256 debt_,
//     uint256 price_
// ) pure returns (uint256 encumberance_) {
//     return price_ != 0 ? Maths.wdiv(Maths.wmul(COLLATERALIZATION_FACTOR , debt_), price_) : 0;
// }

// /**
//  *  @notice Calculates collateralization for a given debt and collateral amounts, at a given price.
//  *  @param  debt_       The debt amount.
//  *  @param  collateral_ The collateral amount.
//  *  @param  price_      The price to calculate collateralization at.
//  *  @return Collateralization value. `1 WAD` if debt amount is `0`.
//  */
// function _collateralization(
//     uint256 debt_,
//     uint256 collateral_,
//     uint256 price_
// ) pure returns (uint256) {
//     // cannot be undercollateralized if there is no debt
//     if (debt_ == 0) return 1e18;

//     // borrower is undercollateralized when lup at MIN_PRICE
//     if (price_ == MIN_PRICE) return 0;
//     return Maths.wdiv(Maths.wmul(collateral_, price_), Maths.wmul(COLLATERALIZATION_FACTOR, debt_));
// }

// /**
//  *  @notice Calculates target utilization for given `EMA` values.
//  *  @param  debtColEma_   The `EMA` of debt squared to collateral.
//  *  @param  lupt0DebtEma_ The `EMA` of `LUP * t0 debt`.
//  *  @return Target utilization of the pool.
//  */
// function _targetUtilization(
//     uint256 debtColEma_,
//     uint256 lupt0DebtEma_
// ) pure returns (uint256) {
//     return (lupt0DebtEma_ != 0) ? Maths.wdiv(debtColEma_, lupt0DebtEma_) : Maths.WAD;

// }
"
    },
    "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);
}
"
    },
    "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.
     *
     * Thi

Tags:
ERC20, Multisig, Burnable, Pausable, Liquidity, Staking, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x16316744f4f7a3a4bc27ce065ab564e54dbb0cde|verified:true|block:23671555|tx:0x986f347cc615122ccdaae2c75c93c80e999ede2c98d9614d36db6b79ab212702|first_check:1761642856

Submitted on: 2025-10-28 10:14:18

Comments

Log in to comment.

No comments yet.