LiquidationProxy

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

pragma solidity ^0.8.20;

import {IERC20Pool} from "./interfaces/pool/erc20/IERC20Pool.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IPoolInfoUtils} from "./interfaces/IPoolInfoUtils.sol";
import {ILVToken} from "./interfaces/ILVToken.sol";
import {UD60x18} from "lib/prb-math/src/ud60x18/ValueType.sol";
import {wrap, unwrap} from "lib/prb-math/src/ud60x18/Casting.sol";
import {mul} from "lib/prb-math/src/ud60x18/Math.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";

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

interface ILVLidoVault {
    function mintForProxy(address token, address receiver, uint256 amount) external returns (bool);
    function burnForProxy(address token, address account, uint256 amount) external returns (bool);
    function lenderKick(uint256 bondAmount) external;
    function transferForProxy(address token, address recipient, uint256 amount) external returns (bool);
    function withdrawBondsForProxy() external returns (uint256);
}

contract LiquidationProxy is Ownable, ReentrancyGuard {
    IERC20Pool public pool;
    IPoolInfoUtils public constant poolInfoUtils = IPoolInfoUtils(0x30c5eF2997d6a882DE52c4ec01B6D0a5e5B4fAAE);
    uint256 constant MIN_BOND_FACTOR = 0.005 * 1e18;
    uint256 constant MAX_BOND_FACTOR = 0.03 * 1e18;
    bool public allowKick = false;
    address public constant quoteToken = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant collateralToken = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
    ILVToken public testCollateralToken;
    ILVToken public testQuoteToken;
    ILVLidoVault public LVLidoVault;
    // Kicker tracking
    uint256 currentBondAmount;
    mapping(address => uint256) public kickerAmount;
    address currentKicker;

    event KickByVault(address sender, uint256 bondSize);
    event PurchaseLVToken(uint256 price, uint256 quoteTokenAmount, uint256 collateralAmount, address sender);
    event AllowKickSet(bool allowKick);
    event AuctionSettled(
        address indexed borrower,
        uint256 collateralSettled,
        bool isBorrowerSettled,
        uint256 remainingDebt,
        uint256 timestamp
    );

    constructor(address _pool) Ownable(msg.sender) {
        pool = IERC20Pool(_pool);
        testCollateralToken = ILVToken(pool.collateralAddress());
        testQuoteToken = ILVToken(pool.quoteTokenAddress());
    }

    function settle(uint256 maxDepth_) external nonReentrant returns (uint256, bool) {
        // Get auction status from PoolInfoUtils.
        // auctionStatus returns: kickTime, collateral, debtToCover, isCollateralized, price, neutralPrice, referencePrice, debtToCollateral, bondFactor.
        (
            uint256 kickTime,
            uint256 collateral,
            uint256 debtToCover,
            bool isCollateralized,
            uint256 price,
            uint256 neutralPrice,
            uint256 referencePrice,
            uint256 debtToCollateral,
            uint256 bondFactor
        ) = auctionStatus();

        require(
            (allowKick && (debtToCover == 0 || collateral == 0 || block.timestamp > kickTime + 72 hours)),
            "Cannot settle auction."
        );

        (uint256 collateralSettled, bool isBorrowerSettled) = pool.settle(address(LVLidoVault), maxDepth_);

        if (isBorrowerSettled) {
            // Store kicker info before resetting
            address kicker = currentKicker;
            uint256 bondAmount = currentBondAmount;

            // Reset kicker state
            currentKicker = address(0);
            currentBondAmount = 0;
            allowKick = false;

            // Remove bond
            (uint256 claimable, uint256 locked) = pool.kickerInfo(address(LVLidoVault));
            require(locked == 0);
            uint256 withdrawnAmount_ = LVLidoVault.withdrawBondsForProxy();
            if (withdrawnAmount_ > 0) {
                // Unwrap tokens as needed and transfer the bond reward to the kicker.
                require(
                    LVLidoVault.burnForProxy(address(testQuoteToken), address(LVLidoVault), withdrawnAmount_),
                    "Burn failed."
                );
                uint256 initialKickerAmount = kickerAmount[kicker];
                // Reset auction state
                if (withdrawnAmount_ > initialKickerAmount) {
                    // Kicker bond grew
                    uint256 extraAmount = withdrawnAmount_ - initialKickerAmount;
                    require(LVLidoVault.transferForProxy(quoteToken, address(this), extraAmount), "Transfer failure.");
                    kickerAmount[kicker] += extraAmount;
                }
            }
        }

        // Emit an event documenting the auction settlement.
        emit AuctionSettled(address(LVLidoVault), collateralSettled, isBorrowerSettled, debtToCover, block.timestamp);

        return (collateralSettled, isBorrowerSettled);
    }

    function auctionStatus()
        public
        view
        returns (
            uint256 kickTime_,
            uint256 collateral_,
            uint256 debtToCover_,
            bool isCollateralized_,
            uint256 price_,
            uint256 neutralPrice_,
            uint256 referencePrice_,
            uint256 debtToCollateral_,
            uint256 bondFactor_
        )
    {
        (
            kickTime_,
            collateral_,
            debtToCover_,
            isCollateralized_,
            price_,
            neutralPrice_,
            referencePrice_,
            debtToCollateral_,
            bondFactor_
        ) = poolInfoUtils.auctionStatus(address(pool), address(LVLidoVault));
        return (
            kickTime_,
            collateral_,
            debtToCover_,
            isCollateralized_,
            price_,
            neutralPrice_,
            referencePrice_,
            debtToCollateral_,
            bondFactor_
        );
    }

    function setLVLidoVault(address _LVLidoVault) public onlyOwner {
        LVLidoVault = ILVLidoVault(_LVLidoVault);
    }

    function setAllowKick(bool _allowKick) external onlyOwner {
        allowKick = _allowKick;
        emit AllowKickSet(_allowKick);
    }

    // Kick to start AUCTION
    function lenderKick() public {
        //require(eligibleForLiquidationPool(_borrower), "Ineligible for liquidation");
        require(allowKick, "Kick not allowed");
        // 3 Scenarios to liquidate in:
        // 1. Borrowers are undercollateralized based on redemption price (collateral lender, else auction)
        // 2. Borrowers are overutilized based on Ajna pool utilization (collateral lender, else auction)
        // 3. Borrowers are undercollateralized based on Ajna bucket price (auction) (redemption price higher than bucket price)

        uint256 bondAmount = getBondSize();

        // Transfer from user to this contract
        require(IERC20(quoteToken).transferFrom(msg.sender, address(this), bondAmount), "Transfer failed");
        LVLidoVault.lenderKick(bondAmount);

        // Track the actual kicker
        currentBondAmount = bondAmount;
        kickerAmount[msg.sender] += bondAmount;
        currentKicker = msg.sender;

        emit KickByVault(msg.sender, bondAmount);
    }

    function take(uint256 collateralToPurchase) external returns (uint256) {
        // Get auction price and calculate quote token amount needed
        (,, uint256 debtToCover,, uint256 auctionPrice,,,,) = auctionStatus();
        require(debtToCover > 0, "Auction not ongoing.");

        // FIXME Scale quoteTokenPayment to mint amount of debt tokens corresponding to actualDebt, not debtToCover
        // We can't do that currently because we don't have the rate before the term ends
        // For now, set total borrow amount to 0 if debt is paid off in full
        UD60x18 collateralAmount = wrap(collateralToPurchase);
        UD60x18 price = wrap(auctionPrice);
        UD60x18 quoteTokenPaymentUD60x18 = mul(collateralAmount, price);
        uint256 quoteTokenPayment = unwrap(quoteTokenPaymentUD60x18) + 1 wei;
        // uint256 quoteTokenPayment = (collateralToPurchase * auctionPrice) / 1e18;

        require(
            LVLidoVault.mintForProxy(address(testQuoteToken), address(this), quoteTokenPayment)
                && IERC20(address(testQuoteToken)).approve(address(pool), quoteTokenPayment),
            "Take failure."
        );

        uint256 collateralTaken = pool.take(address(LVLidoVault), collateralToPurchase, address(this), "");

        // Calculate transfer amount using PRBMath
        UD60x18 collateralTakenAmount = wrap(collateralTaken);
        UD60x18 transferAmountUD60x18 = mul(collateralTakenAmount, price);
        uint256 transferAmount = unwrap(transferAmountUD60x18);

        // FIXME Transfer quote tokens from user and mint scaled equivalent amount
        require(
            IERC20(quoteToken).transferFrom(msg.sender, address(LVLidoVault), transferAmount),
            "Transfer from user failed"
        );

        // console.log("LiqProxy Collateral Balance:", testCollateralToken.balanceOf(address(this)));
        require(
            LVLidoVault.transferForProxy(collateralToken, msg.sender, collateralTaken)
                && LVLidoVault.burnForProxy(address(testCollateralToken), address(this), collateralTaken),
            "Transfer or burn failed."
        );

        // Check if auction is settled
        (uint256 kickTime,,,,,,,,) = auctionStatus();
        bool isBorrowerSettled;
        (,, debtToCover,,,,,,) = auctionStatus();
        // console.log("debtToCover:", debtToCover);

        if (kickTime == 0) {
            // Remove bond
            (uint256 claimable, uint256 locked) = pool.kickerInfo(address(LVLidoVault));
            require(locked == 0);
            uint256 withdrawnAmount_ = LVLidoVault.withdrawBondsForProxy();
            if (withdrawnAmount_ > 0) {
                // Unwrap tokens as needed and transfer the bond reward to the kicker.
                require(
                    LVLidoVault.burnForProxy(address(testQuoteToken), address(LVLidoVault), withdrawnAmount_),
                    "Burn failed."
                );
                uint256 initialKickerAmount = kickerAmount[currentKicker];
                // Reset auction state
                if (withdrawnAmount_ > initialKickerAmount) {
                    // Kicker bond grew
                    uint256 extraAmount = withdrawnAmount_ - initialKickerAmount;
                    require(LVLidoVault.transferForProxy(quoteToken, address(this), extraAmount), "Transfer failure.");
                    kickerAmount[currentKicker] += extraAmount;
                }
            }
            currentBondAmount = 0;
            currentKicker = address(0);
            allowKick = false;
        }
        return collateralTaken;
    }

    function getBondSize() public view returns (uint256) {
        (uint256 debt, uint256 collateral, uint256 npTpRatio) = pool.borrowerInfo(address(LVLidoVault));
        (, uint256 bondSize_) = _bondParams(debt, npTpRatio);
        return bondSize_ + 1 wei;
    }

    function _bondParams(uint256 borrowerDebt_, uint256 npTpRatio_)
        internal
        pure
        returns (uint256 bondFactor_, uint256 bondSize_)
    {
        // Calculate bond factor using PRBMath
        UD60x18 npTpRatio = wrap(npTpRatio_);
        UD60x18 maxBondFactor = wrap(MAX_BOND_FACTOR);
        UD60x18 minBondFactor = wrap(MIN_BOND_FACTOR);

        // Calculate (npTpRatio - 1e18) / 10
        UD60x18 ratioDiff = wrap((npTpRatio_ - 1e18) / 10);

        // Use min and max operations
        UD60x18 tempFactor = ratioDiff.unwrap() > MAX_BOND_FACTOR ? maxBondFactor : ratioDiff;
        bondFactor_ = tempFactor.unwrap() < MIN_BOND_FACTOR ? MIN_BOND_FACTOR : tempFactor.unwrap();

        // Calculate bond size using PRBMath multiplication
        UD60x18 bondFactor = wrap(bondFactor_);
        UD60x18 borrowerDebt = wrap(borrowerDebt_);
        bondSize_ = unwrap(mul(bondFactor, borrowerDebt));
    }

    function eligibleForLiquidationPool(address _borrower) public view returns (bool) {
        uint256 lup = poolInfoUtils.lup(address(pool));
        (uint256 debt_, uint256 collateral_, uint256 t0Np_, uint256 thresholdPrice_) =
            poolInfoUtils.borrowerInfo(address(pool), _borrower);

        if (lup < thresholdPrice_) {
            return true;
        }
        return false;
    }

    function claimBond() public returns (uint256) {
        // Get bond amount locally
        uint256 bondAmount = kickerAmount[msg.sender];
        require(bondAmount > 0, "No bond to claim");
        // Reset kicker state
        kickerAmount[msg.sender] = 0;
        // Transfer bond to user
        // console.log("WETH Balance", IERC20(quoteToken).balanceOf(address(this)));
        IERC20(quoteToken).transfer(msg.sender, bondAmount);
        return bondAmount;
    }
}
"
    },
    "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/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * Both values are immutable: they can only be set once during construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner`'s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @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);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "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/ILVToken.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Author: Lendvest

pragma solidity ^0.8.20;

interface ILVToken {
    // Standard ERC20 functions
    function transfer(address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    // Additional functions for collateral token management
    function mint(address account, uint256 amount) external returns (bool);
    function burn(address from, uint256 amount) external returns (bool);
}
"
    },
    "lib/prb-math/src/ud60x18/ValueType.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;

/// @notice The unsigned 60.18-decimal fixed-point number representation, which can have up to 60 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the Solidity type uint256.
/// @dev The value type is defined here so it can be imported in all other files.
type UD60x18 is uint256;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD1x18,
    Casting.intoSD21x18,
    Casting.intoSD59x18,
    Casting.intoUD2x18,
    Casting.intoUD21x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
    Math.avg,
    Math.ceil,
    Math.div,
    Math.exp,
    Math.exp2,
    Math.floor,
    Math.frac,
    Math.gm,
    Math.inv,
    Math.ln,
    Math.log10,
    Math.log2,
    Math.mul,
    Math.pow,
    Math.powu,
    Math.sqrt
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
    Helpers.add,
    Helpers.and,
    Helpers.eq,
    Helpers.gt,
    Helpers.gte,
    Helpers.isZero,
    Helpers.lshift,
    Helpers.lt,
    Helpers.lte,
    Helpers.mod,
    Helpers.neq,
    Helpers.not,
    Helpers.or,
    Helpers.rshift,
    Helpers.sub,
    Helpers.uncheckedAdd,
    Helpers.uncheckedSub,
    Helpers.xor
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                    OPERATORS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes it possible to use these operators on the UD60x18 type.
using {
    Helpers.add as +,
    Helpers.and2 as &,
    Math.div as /,
    Helpers.eq as ==,
    Helpers.gt as >,
    Helpers.gte as >=,
    Helpers.lt as <,
    Helpers.lte as <=,
    Helpers.or as |,
    Helpers.mod as %,
    Math.mul as *,
    Helpers.neq as !=,
    Helpers.not as ~,
    Helpers.sub as -,
    Helpers.xor as ^
} for UD60x18 global;
"
    },
    "lib/prb-math/src/ud60x18/Casting.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_SD21x18 } from "../sd21x18/Constants.sol";
import { SD21x18 } from "../sd21x18/ValueType.sol";
import { uMAX_SD59x18 } from "../sd59x18/Constants.sol";
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { uMAX_UD21x18 } from "../ud21x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD21x18 } from "../ud21x18/ValueType.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Casts a UD60x18 number into SD1x18.
/// @dev Requirements:
/// - x ≤ uMAX_SD1x18
function intoSD1x18(UD60x18 x) pure returns (SD1x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(int256(uMAX_SD1x18))) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD1x18_Overflow(x);
    }
    result = SD1x18.wrap(int64(uint64(xUint)));
}

/// @notice Casts a UD60x18 number into SD21x18.
/// @dev Requirements:
/// - x ≤ uMAX_SD21x18
function intoSD21x18(UD60x18 x) pure returns (SD21x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(int256(uMAX_SD21x18))) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD21x18_Overflow(x);
    }
    result = SD21x18.wrap(int128(uint128(xUint)));
}

/// @notice Casts a UD60x18 number into UD2x18.
/// @dev Requirements:
/// - x ≤ uMAX_UD2x18
function intoUD2x18(UD60x18 x) pure returns (UD2x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uMAX_UD2x18) {
        revert CastingErrors.PRBMath_UD60x18_IntoUD2x18_Overflow(x);
    }
    result = UD2x18.wrap(uint64(xUint));
}

/// @notice Casts a UD60x18 number into UD21x18.
/// @dev Requirements:
/// - x ≤ uMAX_UD21x18
function intoUD21x18(UD60x18 x) pure returns (UD21x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uMAX_UD21x18) {
        revert CastingErrors.PRBMath_UD60x18_IntoUD21x18_Overflow(x);
    }
    result = UD21x18.wrap(uint128(xUint));
}

/// @notice Casts a UD60x18 number into SD59x18.
/// @dev Requirements:
/// - x ≤ uMAX_SD59x18
function intoSD59x18(UD60x18 x) pure returns (SD59x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(uMAX_SD59x18)) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD59x18_Overflow(x);
    }
    result = SD59x18.wrap(int256(xUint));
}

/// @notice Casts a UD60x18 number into uint128.
/// @dev This is basically an alias for {unwrap}.
function intoUint256(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x);
}

/// @notice Casts a UD60x18 number into uint128.
/// @dev Requirements:
/// - x ≤ MAX_UINT128
function intoUint128(UD60x18 x) pure returns (uint128 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > MAX_UINT128) {
        revert CastingErrors.PRBMath_UD60x18_IntoUint128_Overflow(x);
    }
    result = uint128(xUint);
}

/// @notice Casts a UD60x18 number into uint40.
/// @dev Requirements:
/// - x ≤ MAX_UINT40
function intoUint40(UD60x18 x) pure returns (uint40 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > MAX_UINT40) {
        revert CastingErrors.PRBMath_UD60x18_IntoUint40_Overflow(x);
    }
    result = uint40(xUint);
}

/// @notice Alias for {wrap}.
function ud(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

/// @notice Alias for {wrap}.
function ud60x18(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

/// @notice Unwraps a UD60x18 number into uint256.
function unwrap(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x);
}

/// @notice Wraps a uint256 number into the UD60x18 value type.
function wrap(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}
"
    },
    "lib/prb-math/src/ud60x18/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { wrap } from "./Casting.sol";
import {
    uEXP_MAX_INPUT,
    uEXP2_MAX_INPUT,
    uHALF_UNIT,
    uLOG2_10,
    uLOG2_E,
    uMAX_UD60x18,
    uMAX_WHOLE_UD60x18,
    UNIT,
    uUNIT,
    uUNIT_SQUARED,
    ZERO
} from "./Constants.sol";
import { UD60x18 } from "./ValueType.sol";

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Calculates the arithmetic average of x and y using the following formula:
///
/// $$
/// avg(x, y) = (x & y) + ((xUint ^ yUint) / 2)
/// $$
///
/// In English, this is what this formula does:
///
/// 1. AND x and y.
/// 2. Calculate half of XOR x and y.
/// 3. Add the two results together.
///
/// This technique is known as SWAR, which stands for "SIMD within a register". You can read more about it here:
/// https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The arithmetic average as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();
    unchecked {
        result = wrap((xUint & yUint) + ((xUint ^ yUint) >> 1));
    }
}

/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev This is optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x ≤ MAX_WHOLE_UD60x18
///
/// @param x The UD60x18 number to ceil.
/// @return result The smallest whole number greater than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    if (xUint > uMAX_WHOLE_UD60x18) {
        revert Errors.PRBMath_UD60x18_Ceil_Overflow(x);
    }

    assembly ("memory-safe") {
        // Equivalent to `x % UNIT`.
        let remainder := mod(x, uUNIT)

        // Equivalent to `UNIT - remainder`.
        let delta := sub(uUNIT, remainder)

        // Equivalent to `x + remainder > 0 ? delta : 0`.
        result := add(x, mul(delta, gt(remainder, 0)))
    }
}

/// @notice Divides two UD60x18 numbers, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @param x The numerator as a UD60x18 number.
/// @param y The denominator as a UD60x18 number.
/// @return result The quotient as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(Common.mulDiv(x.unwrap(), uUNIT, y.unwrap()));
}

/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Requirements:
/// - x ≤ 133_084258667509499440
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    // This check prevents values greater than 192e18 from being passed to {exp2}.
    if (xUint > uEXP_MAX_INPUT) {
        revert Errors.PRBMath_UD60x18_Exp_InputTooBig(x);
    }

    unchecked {
        // Inline the fixed-point multiplication to save gas.
        uint256 doubleUnitProduct = xUint * uLOG2_E;
        result = exp2(wrap(doubleUnitProduct / uUNIT));
    }
}

/// @notice Calculates the binary exponent of x using the binary fraction method.
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693
///
/// Requirements:
/// - x < 192e18
/// - The result must fit in UD60x18.
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
    if (xUint > uEXP2_MAX_INPUT) {
        revert Errors.PRBMath_UD60x18_Exp2_InputTooBig(x);
    }

    // Convert x to the 192.64-bit fixed-point format.
    uint256 x_192x64 = (xUint << 64) / uUNIT;

    // Pass x to the {Common.exp2} function, which uses the 192.64-bit fixed-point number representation.
    result = wrap(Common.exp2(x_192x64));
}

/// @notice Yields the greatest whole number less than or equal to x.
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
/// @param x The UD60x18 number to floor.
/// @return result The greatest whole number less than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(UD60x18 x) pure returns (UD60x18 result) {
    assembly ("memory-safe") {
        // Equivalent to `x % UNIT`.
        let remainder := mod(x, uUNIT)

        // Equivalent to `x - remainder > 0 ? remainder : 0)`.
        result := sub(x, mul(remainder, gt(remainder, 0)))
    }
}

/// @notice Yields the excess beyond the floor of x using the odd function definition.
/// @dev See https://en.wikipedia.org/wiki/Fractional_part.
/// @param x The UD60x18 number to get the fractional part of.
/// @return result The fractional part of x as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function frac(UD60x18 x) pure returns (UD60x18 result) {
    assembly ("memory-safe") {
        result := mod(x, uUNIT)
    }
}

/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$, rounding down.
///
/// @dev Requirements:
/// - x * y must fit in UD60x18.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();
    if (xUint == 0 || yUint == 0) {
        return ZERO;
    }

    unchecked {
        // Checking for overflow this way is faster than letting Solidity do it.
        uint256 xyUint = xUint * yUint;
        if (xyUint / xUint != yUint) {
            revert Errors.PRBMath_UD60x18_Gm_Overflow(x, y);
        }

        // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
        // during multiplication. See the comments in {Common.sqrt}.
        result = wrap(Common.sqrt(xyUint));
    }
}

/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The UD60x18 number for which to calculate the inverse.
/// @return result The inverse as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(UD60x18 x) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(uUNIT_SQUARED / x.unwrap());
    }
}

/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(UD60x18 x) pure returns (UD60x18 result) {
    unchecked {
        // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
        // {log2} can return is ~196_205294292027477728.
        result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
    }
}

/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    if (xUint < uUNIT) {
        revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
    }

    // Note that the `mul` in this assembly block is the standard multiplication operation, not {UD60x18.mul}.
    // prettier-ignore
    assembly ("memory-safe") {
        switch x
        case 1 { result := mul(uUNIT, sub(0, 18)) }
        case 10 { result := mul(uUNIT, sub(1, 18)) }
        case 100 { result := mul(uUNIT, sub(2, 18)) }
        case 1000 { result := mul(uUNIT, sub(3, 18)) }
        case 10000 { result := mul(uUNIT, sub(4, 18)) }
        case 100000 { result := mul(uUNIT, sub(5, 18)) }
        case 1000000 { result := mul(uUNIT, sub(6, 18)) }
        case 10000000 { result := mul(uUNIT, sub(7, 18)) }
        case 100000000 { result := mul(uUNIT, sub(8, 18)) }
        case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
        case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
        case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
        case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
        case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
        case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
        case 1

Tags:
ERC20, Multisig, Mintable, Burnable, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x1b910280605493db53a802df04b497fcda4293ac|verified:true|block:23671289|tx:0x63cab3f2445ba818822f365368e57c182df8f4ef95b9397e7d2644ad78cf9cdf|first_check:1761642334

Submitted on: 2025-10-28 10:05:36

Comments

Log in to comment.

No comments yet.