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);
/**
Submitted on: 2025-10-28 10:08:15
Comments
Log in to comment.
No comments yet.