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
Submitted on: 2025-10-28 10:14:18
Comments
Log in to comment.
No comments yet.