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