Overview
ETH Balance
0 ETH
Eth Value
$0.00Token Holdings
More Info
Private Name Tags
ContractCreator
Latest 25 from a total of 62 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Multicall | 21082276 | 5 hrs ago | IN | 0 ETH | 0.00494995 | ||||
Remove Quote Tok... | 20873027 | 29 days ago | IN | 0 ETH | 0.00729828 | ||||
Add Collateral | 20873024 | 29 days ago | IN | 0 ETH | 0.00781797 | ||||
Multicall | 20868237 | 30 days ago | IN | 0 ETH | 0.03491593 | ||||
Remove Quote Tok... | 20849563 | 32 days ago | IN | 0 ETH | 0.00336341 | ||||
Add Collateral | 20849554 | 32 days ago | IN | 0 ETH | 0.00332255 | ||||
Multicall | 20847419 | 32 days ago | IN | 0 ETH | 0.00561974 | ||||
Move Quote Token | 20599146 | 67 days ago | IN | 0 ETH | 0.00075824 | ||||
Add Quote Token | 20599138 | 67 days ago | IN | 0 ETH | 0.00054535 | ||||
Update Interest | 20486092 | 83 days ago | IN | 0 ETH | 0.00140192 | ||||
Remove Quote Tok... | 20465761 | 86 days ago | IN | 0 ETH | 0.00113171 | ||||
Add Quote Token | 20465748 | 86 days ago | IN | 0 ETH | 0.00076385 | ||||
Remove Quote Tok... | 20465740 | 86 days ago | IN | 0 ETH | 0.00077567 | ||||
Add Collateral | 20465736 | 86 days ago | IN | 0 ETH | 0.00061308 | ||||
Add Quote Token | 20465672 | 86 days ago | IN | 0 ETH | 0.00062676 | ||||
Move Quote Token | 20465629 | 86 days ago | IN | 0 ETH | 0.00098655 | ||||
Remove Quote Tok... | 20465550 | 86 days ago | IN | 0 ETH | 0.00118025 | ||||
Approve LP Trans... | 20465535 | 86 days ago | IN | 0 ETH | 0.00012764 | ||||
Update Interest | 20430044 | 91 days ago | IN | 0 ETH | 0.00120448 | ||||
Remove Quote Tok... | 20404909 | 94 days ago | IN | 0 ETH | 0.00060585 | ||||
Update Interest | 20404826 | 94 days ago | IN | 0 ETH | 0.00047685 | ||||
Update Interest | 20355704 | 101 days ago | IN | 0 ETH | 0.00212182 | ||||
Update Interest | 20355385 | 101 days ago | IN | 0 ETH | 0.00121843 | ||||
Update Interest | 20337573 | 104 days ago | IN | 0 ETH | 0.002133 | ||||
Update Interest | 20312264 | 107 days ago | IN | 0 ETH | 0.00154007 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
19207429 | 262 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Minimal Proxy Contract for 0x4b3b9a972a884e37fd675ee57f6998da0be47e26
Contract Name:
ERC20Pool
Compiler Version
v0.8.18+commit.87f61d96
Optimization Enabled:
Yes with 0 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Pool, IERC20PoolBorrowerActions, IERC20PoolImmutables, IERC20PoolLenderActions } from './interfaces/pool/erc20/IERC20Pool.sol'; import { IERC20Taker } from './interfaces/pool/erc20/IERC20Taker.sol'; import { IPoolLenderActions, IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions } from './interfaces/pool/IPool.sol'; import { IERC3156FlashBorrower, IERC3156FlashLender } from './interfaces/pool/IERC3156FlashLender.sol'; import { DrawDebtResult, RepayDebtResult, SettleParams, SettleResult, TakeResult } from './interfaces/pool/commons/IPoolInternals.sol'; import { PoolState } from './interfaces/pool/commons/IPoolState.sol'; import { FlashloanablePool } from './base/FlashloanablePool.sol'; import { _getCollateralDustPricePrecisionAdjustment, _roundToScale, _roundUpToScale } from './libraries/helpers/PoolHelper.sol'; import { _revertIfAuctionClearable, _revertAfterExpiry } from './libraries/helpers/RevertsHelper.sol'; import { Loans } from './libraries/internal/Loans.sol'; import { Deposits } from './libraries/internal/Deposits.sol'; import { Maths } from './libraries/internal/Maths.sol'; import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; import { LenderActions } from './libraries/external/LenderActions.sol'; import { SettlerActions } from './libraries/external/SettlerActions.sol'; import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC20 Pool contract * @notice Entrypoint of `ERC20` Pool actions for pool actors: * - `Lenders`: add, remove and move quote tokens; transfer `LP` * - `Borrowers`: draw and repay debt * - `Traders`: add, remove and move quote tokens; add and remove collateral * - `Kickers`: kick undercollateralized loans; settle auctions; claim bond rewards * - `Bidders`: take auctioned collateral * - `Reserve purchasers`: start auctions; take reserves * - `Flash borrowers`: initiate flash loans on quote tokens and collateral * @dev Contract is `FlashloanablePool` with flash loan logic. * @dev Contract is base `Pool` with logic to handle `ERC20` collateral. * @dev Calls logic from external `PoolCommons`, `LenderActions`, `BorrowerActions` and `Auction` actions libraries. */ contract ERC20Pool is FlashloanablePool, IERC20Pool { using SafeERC20 for IERC20; /*****************/ /*** Constants ***/ /*****************/ /// @dev Immutable collateral scale arg offset. uint256 internal constant COLLATERAL_SCALE = 93; /****************************/ /*** Initialize Functions ***/ /****************************/ /// @inheritdoc IERC20Pool function initialize( uint256 rate_ ) external override { if (isPoolInitialized) revert AlreadyInitialized(); inflatorState.inflator = uint208(1e18); inflatorState.inflatorUpdate = uint48(block.timestamp); interestState.interestRate = uint208(rate_); interestState.interestRateUpdate = uint48(block.timestamp); Loans.init(loans); // increment initializations count to ensure these values can't be updated isPoolInitialized = true; } /******************/ /*** Immutables ***/ /******************/ /// @inheritdoc IERC20PoolImmutables function collateralScale() external pure override returns (uint256) { return _getArgUint256(COLLATERAL_SCALE); } /// @inheritdoc IERC20Pool function bucketCollateralDust(uint256 bucketIndex_) external pure override returns (uint256) { return _bucketCollateralDust(bucketIndex_); } /***********************************/ /*** Borrower External Functions ***/ /***********************************/ /** * @inheritdoc IERC20PoolBorrowerActions * @dev === Write state === * @dev - increment `poolBalances.pledgedCollateral` accumulator * @dev - increment `poolBalances.t0Debt` accumulator * @dev - update `t0Debt2ToCollateral` ratio * @dev === Emit events === * @dev - `DrawDebt` */ function drawDebt( address borrowerAddress_, uint256 amountToBorrow_, uint256 limitIndex_, uint256 collateralToPledge_ ) external nonReentrant { PoolState memory poolState = _accruePoolInterest(); // ensure the borrower is not charged for additional debt that they did not receive amountToBorrow_ = _roundToScale(amountToBorrow_, poolState.quoteTokenScale); // ensure the borrower is not credited with a fractional amount of collateral smaller than the token scale collateralToPledge_ = _roundToScale(collateralToPledge_, _getArgUint256(COLLATERAL_SCALE)); DrawDebtResult memory result = BorrowerActions.drawDebt( auctions, deposits, loans, poolState, _availableQuoteToken(), borrowerAddress_, amountToBorrow_, limitIndex_, collateralToPledge_ ); emit DrawDebt(borrowerAddress_, amountToBorrow_, collateralToPledge_, result.newLup); // update in memory pool state struct poolState.debt = result.poolDebt; poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio _updateT0Debt2ToCollateral( result.debtPreAction, result.debtPostAction, result.collateralPreAction, result.collateralPostAction ); // update pool interest rate state _updateInterestState(poolState, result.newLup); if (collateralToPledge_ != 0) { // update pool balances pledged collateral state poolBalances.pledgedCollateral = poolState.collateral; // move collateral from sender to pool _transferCollateralFrom(msg.sender, collateralToPledge_); } if (amountToBorrow_ != 0) { // update pool balances t0 debt state poolBalances.t0Debt = poolState.t0Debt; // move borrowed amount from pool to sender _transferQuoteToken(msg.sender, amountToBorrow_); } } /** * @inheritdoc IERC20PoolBorrowerActions * @dev === Write state === * @dev - decrement `poolBalances.t0Debt accumulator` * @dev - decrement `poolBalances.pledgedCollateral accumulator` * @dev - update `t0Debt2ToCollateral` ratio * @dev === Emit events === * @dev - `RepayDebt` */ function repayDebt( address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, uint256 collateralAmountToPull_, address collateralReceiver_, uint256 limitIndex_ ) external nonReentrant returns (uint256 amountRepaid_) { PoolState memory poolState = _accruePoolInterest(); // ensure accounting is performed using the appropriate token scale if (maxQuoteTokenAmountToRepay_ != type(uint256).max) maxQuoteTokenAmountToRepay_ = _roundToScale(maxQuoteTokenAmountToRepay_, poolState.quoteTokenScale); collateralAmountToPull_ = _roundToScale(collateralAmountToPull_, _getArgUint256(COLLATERAL_SCALE)); RepayDebtResult memory result = BorrowerActions.repayDebt( auctions, deposits, loans, poolState, borrowerAddress_, maxQuoteTokenAmountToRepay_, collateralAmountToPull_, limitIndex_ ); amountRepaid_ = result.quoteTokenToRepay; // update in memory pool state struct poolState.debt = result.poolDebt; poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; emit RepayDebt(borrowerAddress_, amountRepaid_, collateralAmountToPull_, result.newLup); // adjust t0Debt2ToCollateral ratio _updateT0Debt2ToCollateral( result.debtPreAction, result.debtPostAction, result.collateralPreAction, result.collateralPostAction ); // update pool interest rate state _updateInterestState(poolState, result.newLup); if (amountRepaid_ != 0) { // update pool balances t0 debt state poolBalances.t0Debt = poolState.t0Debt; // move amount to repay from sender to pool _transferQuoteTokenFrom(msg.sender, amountRepaid_); } if (collateralAmountToPull_ != 0) { // update pool balances pledged collateral state poolBalances.pledgedCollateral = poolState.collateral; // move collateral from pool to address specified as collateral receiver _transferCollateral(collateralReceiver_, collateralAmountToPull_); } } /*********************************/ /*** Lender External Functions ***/ /*********************************/ /** * @inheritdoc IERC20PoolLenderActions * @dev === Reverts on === * @dev - `DustAmountNotExceeded()` * @dev === Emit events === * @dev - `AddCollateral` */ function addCollateral( uint256 amountToAdd_, uint256 index_, uint256 expiry_ ) external override nonReentrant returns (uint256 bucketLP_) { _revertAfterExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); // revert if the dust amount was not exceeded, but round on the scale amount if (amountToAdd_ != 0 && amountToAdd_ < _bucketCollateralDust(index_)) revert DustAmountNotExceeded(); amountToAdd_ = _roundToScale(amountToAdd_, _getArgUint256(COLLATERAL_SCALE)); bucketLP_ = LenderActions.addCollateral( buckets, deposits, amountToAdd_, index_ ); emit AddCollateral(msg.sender, index_, amountToAdd_, bucketLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move required collateral from sender to pool _transferCollateralFrom(msg.sender, amountToAdd_); } /** * @inheritdoc IPoolLenderActions * @dev === Emit events === * @dev - `RemoveCollateral` */ function removeCollateral( uint256 maxAmount_, uint256 index_ ) external override nonReentrant returns (uint256 removedAmount_, uint256 redeemedLP_) { _revertIfAuctionClearable(auctions, loans); PoolState memory poolState = _accruePoolInterest(); // round the collateral amount appropriately based on token precision maxAmount_ = _roundToScale(maxAmount_, _getArgUint256(COLLATERAL_SCALE)); (removedAmount_, redeemedLP_) = LenderActions.removeMaxCollateral( buckets, deposits, _bucketCollateralDust(index_), maxAmount_, index_ ); emit RemoveCollateral(msg.sender, index_, removedAmount_, redeemedLP_); // update pool interest rate state _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); // move collateral from pool to lender _transferCollateral(msg.sender, removedAmount_); } /*******************************/ /*** Pool Auctions Functions ***/ /*******************************/ /** * @inheritdoc IPoolSettlerActions * @dev === Write state === * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - no update of `t0Debt2ToCollateral` ratio as debt and collateral pre settle are not taken into account (pre debt and pre collateral = 0) * @dev and loan is removed from auction queue only when there's no more debt (post debt = 0) */ function settle( address borrowerAddress_, uint256 maxDepth_ ) external override nonReentrant returns (uint256 collateralSettled_, bool isBorrowerSettled_) { PoolState memory poolState = _accruePoolInterest(); SettleResult memory result = SettlerActions.settlePoolDebt( auctions, buckets, deposits, loans, reserveAuction, poolState, SettleParams({ borrower: borrowerAddress_, poolBalance: _getNormalizedPoolQuoteTokenBalance(), bucketDepth: maxDepth_ }) ); _updatePostSettleState(result, poolState); collateralSettled_ = result.collateralSettled; isBorrowerSettled_ = (result.debtPostAction == 0); } /** * @inheritdoc IPoolTakerActions * @dev === Write state === * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( address borrowerAddress_, uint256 maxAmount_, address callee_, bytes calldata data_ ) external override nonReentrant returns (uint256 collateralTaken_) { PoolState memory poolState = _accruePoolInterest(); uint256 collateralTokenScale = _getArgUint256(COLLATERAL_SCALE); // round requested collateral to an amount which can actually be transferred maxAmount_ = _roundToScale(maxAmount_, collateralTokenScale); TakeResult memory result = TakerActions.take( auctions, buckets, deposits, loans, poolState, borrowerAddress_, maxAmount_, collateralTokenScale ); // round quote token up to cover the cost of purchasing the collateral result.quoteTokenAmount = _roundUpToScale(result.quoteTokenAmount, poolState.quoteTokenScale); _updatePostTakeState(result, poolState); collateralTaken_ = result.collateralAmount; _transferCollateral(callee_, collateralTaken_); if (data_.length != 0) { IERC20Taker(callee_).atomicSwapCallback( collateralTaken_ / collateralTokenScale, result.quoteTokenAmount / poolState.quoteTokenScale, data_ ); } _transferQuoteTokenFrom(msg.sender, result.quoteTokenAmount); } /** * @inheritdoc IPoolTakerActions * @dev === Write state === * @dev - decrement `poolBalances.t0Debt` accumulator * @dev - decrement `poolBalances.t0DebtInAuction` accumulator * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function bucketTake( address borrowerAddress_, bool depositTake_, uint256 index_ ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); TakeResult memory result = TakerActions.bucketTake( auctions, buckets, deposits, loans, poolState, borrowerAddress_, depositTake_, index_, _getArgUint256(COLLATERAL_SCALE) ); _updatePostTakeState(result, poolState); } /***************************/ /*** Flashloan Functions ***/ /***************************/ /** * @inheritdoc FlashloanablePool * @dev Override default implementation and allows flashloans for both quote and collateral token. */ function _isFlashloanSupported( address token_ ) internal virtual view override returns (bool) { return token_ == _getArgAddress(QUOTE_ADDRESS) || token_ == _getArgAddress(COLLATERAL_ADDRESS); } /************************/ /*** Helper Functions ***/ /************************/ /** * @notice Helper function to transfer amount of collateral tokens from sender to pool contract. * @param from_ Sender address. * @param amount_ Amount to transfer from sender (`WAD` precision). Scaled to collateral precision before transfer. */ function _transferCollateralFrom(address from_, uint256 amount_) internal { // Transfer amount in favour of the pool uint256 transferAmount = Maths.ceilDiv(amount_, _getArgUint256(COLLATERAL_SCALE)); IERC20(_getArgAddress(COLLATERAL_ADDRESS)).safeTransferFrom(from_, address(this), transferAmount); } /** * @notice Helper function to transfer amount of collateral tokens from pool contract. * @param to_ Receiver address. * @param amount_ Amount to transfer to receiver (`WAD` precision). Scaled to collateral precision before transfer. */ function _transferCollateral(address to_, uint256 amount_) internal { IERC20(_getArgAddress(COLLATERAL_ADDRESS)).safeTransfer(to_, amount_ / _getArgUint256(COLLATERAL_SCALE)); } /** * @notice Helper function to calculate the minimum amount of collateral an actor may have in a bucket. * @param bucketIndex_ Bucket index. * @return Amount of collateral dust amount of the bucket. */ function _bucketCollateralDust(uint256 bucketIndex_) internal pure returns (uint256) { // price precision adjustment will always be 0 for encumbered collateral uint256 pricePrecisionAdjustment = _getCollateralDustPricePrecisionAdjustment(bucketIndex_); // difference between the normalized scale and the collateral token's scale return Maths.max(_getArgUint256(COLLATERAL_SCALE), 10 ** pricePrecisionAdjustment); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; import "../extensions/draft-IERC20Permit.sol"; import "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; function safeTransfer( IERC20 token, address to, uint256 value ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom( IERC20 token, address from, address to, uint256 value ) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove( IERC20 token, address spender, uint256 value ) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance( IERC20 token, address spender, uint256 value ) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance( IERC20 token, address spender, uint256 value ) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); uint256 newAllowance = oldAllowance - value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } } function safePermit( IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ 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 amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; 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); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; interface IERC20Taker { /** * @notice Called by `Pool.take` allowing a taker to externally swap collateral for quote token. * @param collateralAmount The denormalized amount of collateral being taken (`WAD` precision). * @param quoteAmountDue Denormalized amount of quote token required to purchase `collateralAmount` at the * current auction price (`WAD` precision). * @param data Taker-provided calldata passed from taker's invocation to their callback. */ function atomicSwapCallback( uint256 collateralAmount, uint256 quoteAmountDue, bytes calldata data ) external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; import { IPoolBorrowerActions } from './commons/IPoolBorrowerActions.sol'; import { IPoolLPActions } from './commons/IPoolLPActions.sol'; import { IPoolLenderActions } from './commons/IPoolLenderActions.sol'; import { IPoolKickerActions } from './commons/IPoolKickerActions.sol'; import { IPoolTakerActions } from './commons/IPoolTakerActions.sol'; import { IPoolSettlerActions } from './commons/IPoolSettlerActions.sol'; import { IPoolImmutables } from './commons/IPoolImmutables.sol'; import { IPoolState } from './commons/IPoolState.sol'; import { IPoolDerivedState } from './commons/IPoolDerivedState.sol'; import { IPoolEvents } from './commons/IPoolEvents.sol'; import { IPoolErrors } from './commons/IPoolErrors.sol'; import { IERC3156FlashLender } from './IERC3156FlashLender.sol'; /** * @title Base Pool Interface */ interface IPool is IPoolBorrowerActions, IPoolLPActions, IPoolLenderActions, IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions, IPoolImmutables, IPoolState, IPoolDerivedState, IPoolEvents, IPoolErrors, IERC3156FlashLender { } /// @dev Pool type enum - `ERC20` and `ERC721` enum PoolType { ERC20, ERC721 } /// @dev `ERC20` token interface. interface IERC20Token { function balanceOf(address account) external view returns (uint256); function burn(uint256 amount) external; function decimals() external view returns (uint8); } /// @dev `ERC721` token interface. interface IERC721Token { function transferFrom( address from, address to, uint256 tokenId ) external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; import { IERC3156FlashBorrower } from "./IERC3156FlashBorrower.sol"; interface IERC3156FlashLender { /** * @dev The amount of currency available to be lent. * @param token_ The loan currency. * @return The amount of `token` that can be borrowed (token precision). */ function maxFlashLoan( address token_ ) external view returns (uint256); /** * @dev The fee to be charged for a given loan. * @param token_ The loan currency. * @param amount_ The amount of tokens lent (token precision). * @return The amount of `token` to be charged for the loan (token precision), on top of the returned principal . */ function flashFee( address token_, uint256 amount_ ) external view returns (uint256); /** * @dev Initiate a flash loan. * @param receiver_ The receiver of the tokens in the loan, and the receiver of the callback. * @param token_ The loan currency. * @param amount_ The amount of tokens lent (token precision). * @param data_ Arbitrary data structure, intended to contain user-defined parameters. * @return `True` when successful flashloan, `false` otherwise. */ function flashLoan( IERC3156FlashBorrower receiver_, address token_, uint256 amount_, bytes calldata data_ ) external returns (bool); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Internal structs used by the pool / libraries */ /*****************************/ /*** Auction Param Structs ***/ /*****************************/ /// @dev Struct used to return result of `KickerAction.kick` action. struct KickResult { uint256 amountToCoverBond; // [WAD] amount of bond that needs to be covered uint256 t0KickedDebt; // [WAD] new t0 debt after kick uint256 collateralPreAction; // [WAD] The amount of borrower collateral before kick, same as the one after kick uint256 lup; // [WAD] current LUP in pool after kick } /// @dev Struct used to hold parameters for `SettlerAction.settlePoolDebt` action. struct SettleParams { address borrower; // borrower address to settle uint256 bucketDepth; // number of buckets to use when settle debt uint256 poolBalance; // current pool quote token balance } /// @dev Struct used to return result of `SettlerAction.settlePoolDebt` action. struct SettleResult { uint256 debtPreAction; // [WAD] The amount of borrower t0 debt before settle uint256 debtPostAction; // [WAD] The amount of borrower t0 debt remaining after settle uint256 collateralPreAction; // [WAD] The amount of borrower collateral before settle uint256 collateralRemaining; // [WAD] The amount of borrower collateral left after settle uint256 collateralSettled; // [WAD] The amount of borrower collateral settled uint256 t0DebtSettled; // [WAD] The amount of t0 debt settled uint256 debtSettled; // [WAD] The amount of actual debt settled } /// @dev Struct used to return result of `TakerAction.take` and `TakerAction.bucketTake` actions. struct TakeResult { uint256 collateralAmount; // [WAD] amount of collateral taken uint256 compensatedCollateral; // [WAD] amount of borrower collateral that is compensated with LP uint256 quoteTokenAmount; // [WAD] amount of quote tokens paid by taker for taken collateral, used in take action uint256 excessQuoteToken; // [WAD] (NFT only) amount of quote tokens to be paid by taker to borrower for fractional collateral, used in take action uint256 remainingCollateral; // [WAD] amount of borrower collateral remaining after take uint256 poolDebt; // [WAD] current pool debt uint256 t0PoolDebt; // [WAD] t0 pool debt uint256 newLup; // [WAD] current lup uint256 t0DebtInAuctionChange; // [WAD] the amount of t0 debt recovered by take action bool settledAuction; // true if auction is settled by take action uint256 debtPreAction; // [WAD] The amount of borrower t0 debt before take uint256 debtPostAction; // [WAD] The amount of borrower t0 debt after take uint256 collateralPreAction; // [WAD] The amount of borrower collateral before take uint256 collateralPostAction; // [WAD] The amount of borrower collateral after take } /// @dev Struct used to hold parameters for `KickerAction.kickReserveAuction` action. struct KickReserveAuctionParams { uint256 poolSize; // [WAD] total deposits in pool (with accrued debt) uint256 t0PoolDebt; // [WAD] current t0 pool debt uint256 poolBalance; // [WAD] pool quote token balance uint256 inflator; // [WAD] pool current inflator } /******************************************/ /*** Liquidity Management Param Structs ***/ /******************************************/ /// @dev Struct used to hold parameters for `LenderAction.addQuoteToken` action. struct AddQuoteParams { uint256 amount; // [WAD] amount to be added uint256 index; // the index in which to deposit } /// @dev Struct used to hold parameters for `LenderAction.moveQuoteToken` action. struct MoveQuoteParams { uint256 fromIndex; // the deposit index from where amount is moved uint256 maxAmountToMove; // [WAD] max amount to move between deposits uint256 toIndex; // the deposit index where amount is moved to uint256 maxT0DebtToCollateral; // [WAD] max t0 debt to collateral in pool } /// @dev Struct used to hold parameters for `LenderAction.removeQuoteToken` action. struct RemoveQuoteParams { uint256 index; // the deposit index from where amount is removed uint256 maxAmount; // [WAD] max amount to be removed uint256 maxT0DebtToCollateral; // [WAD] max t0 debt to collateral in pool } /*************************************/ /*** Loan Management Param Structs ***/ /*************************************/ /// @dev Struct used to return result of `BorrowerActions.drawDebt` action. struct DrawDebtResult { uint256 newLup; // [WAD] new pool LUP after draw debt uint256 poolCollateral; // [WAD] total amount of collateral in pool after pledge collateral uint256 poolDebt; // [WAD] total accrued debt in pool after draw debt uint256 remainingCollateral; // [WAD] amount of borrower collateral after draw debt (for NFT can be diminished if auction settled) uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after draw debt uint256 debtPreAction; // [WAD] The amount of borrower t0 debt before draw debt uint256 debtPostAction; // [WAD] The amount of borrower t0 debt after draw debt uint256 collateralPreAction; // [WAD] The amount of borrower collateral before draw debt uint256 collateralPostAction; // [WAD] The amount of borrower collateral after draw debt } /// @dev Struct used to return result of `BorrowerActions.repayDebt` action. struct RepayDebtResult { uint256 newLup; // [WAD] new pool LUP after draw debt uint256 poolCollateral; // [WAD] total amount of collateral in pool after pull collateral uint256 poolDebt; // [WAD] total accrued debt in pool after repay debt uint256 remainingCollateral; // [WAD] amount of borrower collateral after pull collateral uint256 t0PoolDebt; // [WAD] amount of t0 debt in pool after repay uint256 quoteTokenToRepay; // [WAD] quote token amount to be transferred from sender to pool uint256 debtPreAction; // [WAD] The amount of borrower t0 debt before repay debt uint256 debtPostAction; // [WAD] The amount of borrower t0 debt after repay debt uint256 collateralPreAction; // [WAD] The amount of borrower collateral before repay debt uint256 collateralPostAction; // [WAD] The amount of borrower collateral after repay debt }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool State */ interface IPoolState { /** * @notice Returns details of an auction for a given borrower address. * @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, 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 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 Returns pool related debt values. * @return debt_ Current amount of debt owed by borrowers in pool. * @return accruedDebt_ Debt owed by borrowers based on last inflator snapshot. * @return debtInAuction_ Total amount of debt in auction. * @return t0Debt2ToCollateral_ t0debt accross all borrowers divided by their collateral, used in determining a collateralization weighted debt. */ function debtInfo() external view returns ( uint256 debt_, uint256 accruedDebt_, uint256 debtInAuction_, uint256 t0Debt2ToCollateral_ ); /** * @notice Mapping of borrower addresses to `Borrower` structs. * @dev NOTE: Cannot use appended underscore syntax for return params since struct is used. * @param borrower_ Address of the borrower. * @return t0Debt_ Amount of debt borrower would have had if their loan was the first debt drawn from the pool. * @return collateral_ Amount of collateral that the borrower has deposited, in collateral token. * @return npTpRatio_ Np to Tp ratio of borrower at the time of last borrow or pull collateral. */ function borrowerInfo(address borrower_) external view returns ( uint256 t0Debt_, uint256 collateral_, uint256 npTpRatio_ ); /** * @notice Mapping of buckets indexes to `Bucket` structs. * @dev NOTE: Cannot use appended underscore syntax for return params since struct is used. * @param index_ Bucket index. * @return lpAccumulator_ Amount of `LP` accumulated in current bucket. * @return availableCollateral_ Amount of collateral available in current bucket. * @return bankruptcyTime_ Timestamp when bucket become insolvent, `0` if healthy. * @return bucketDeposit_ Amount of quote tokens in bucket. * @return bucketScale_ Bucket multiplier. */ function bucketInfo(uint256 index_) external view returns ( uint256 lpAccumulator_, uint256 availableCollateral_, uint256 bankruptcyTime_, uint256 bucketDeposit_, uint256 bucketScale_ ); /** * @notice Mapping of burnEventEpoch to `BurnEvent` structs. * @dev Reserve auctions correspond to burn events. * @param burnEventEpoch_ Id of the current reserve auction. * @return burnBlock_ Block in which a reserve auction started. * @return totalInterest_ Total interest as of the reserve auction. * @return totalBurned_ Total ajna tokens burned as of the reserve auction. */ function burnInfo(uint256 burnEventEpoch_) external view returns (uint256, uint256, uint256); /** * @notice Returns the latest `burnEventEpoch` of reserve auctions. * @dev If a reserve auction is active, it refers to the current reserve auction. If no reserve auction is active, it refers to the last reserve auction. * @return Current `burnEventEpoch`. */ function currentBurnEpoch() external view returns (uint256); /** * @notice Returns information about the pool `EMA (Exponential Moving Average)` variables. * @return debtColEma_ Debt squared to collateral Exponential, numerator to `TU` calculation. * @return lupt0DebtEma_ Exponential of `LUP * t0 debt`, denominator to `TU` calculation * @return debtEma_ Exponential debt moving average. * @return depositEma_ sample of meaningful deposit Exponential, denominator to `MAU` calculation. */ function emasInfo() external view returns ( uint256 debtColEma_, uint256 lupt0DebtEma_, uint256 debtEma_, uint256 depositEma_ ); /** * @notice Returns information about pool inflator. * @return inflator_ Pool inflator value. * @return lastUpdate_ The timestamp of the last `inflator` update. */ function inflatorInfo() external view returns ( uint256 inflator_, uint256 lastUpdate_ ); /** * @notice Returns information about pool interest rate. * @return interestRate_ Current interest rate in pool. * @return interestRateUpdate_ The timestamp of the last interest rate update. */ function interestRateInfo() external view returns ( uint256 interestRate_, uint256 interestRateUpdate_ ); /** * @notice Returns details about kicker balances. * @param kicker_ The address of the kicker to retrieved info for. * @return claimable_ Amount of quote token kicker can claim / withdraw from pool at any time. * @return locked_ Amount of quote token kicker locked in auctions (as bonds). */ function kickerInfo(address kicker_) external view returns ( uint256 claimable_, uint256 locked_ ); /** * @notice Mapping of buckets indexes and owner addresses to `Lender` structs. * @param index_ Bucket index. * @param lender_ Address of the liquidity provider. * @return lpBalance_ Amount of `LP` owner has in current bucket. * @return depositTime_ Time the user last deposited quote token. */ function lenderInfo( uint256 index_, address lender_ ) external view returns ( uint256 lpBalance_, uint256 depositTime_ ); /** * @notice Return the `LP` allowance a `LP` owner provided to a spender. * @param index_ Bucket index. * @param spender_ Address of the `LP` spender. * @param owner_ The initial owner of the `LP`. * @return allowance_ Amount of `LP` spender can utilize. */ function lpAllowance( uint256 index_, address spender_, address owner_ ) external view returns (uint256 allowance_); /** * @notice Returns information about a loan in the pool. * @param loanId_ Loan's id within loan heap. Max loan is position `1`. * @return borrower_ Borrower address at the given position. * @return t0DebtToCollateral_ Borrower t0 debt to collateral. */ function loanInfo( uint256 loanId_ ) external view returns ( address borrower_, uint256 t0DebtToCollateral_ ); /** * @notice Returns information about pool loans. * @return maxBorrower_ Borrower address with highest t0 debt to collateral. * @return maxT0DebtToCollateral_ Highest t0 debt to collateral in pool. * @return noOfLoans_ Total number of loans. */ function loansInfo() external view returns ( address maxBorrower_, uint256 maxT0DebtToCollateral_, uint256 noOfLoans_ ); /** * @notice Returns information about pool reserves. * @return liquidationBondEscrowed_ Amount of liquidation bond across all liquidators. * @return reserveAuctionUnclaimed_ Amount of claimable reserves which has not been taken in the `Claimable Reserve Auction`. * @return reserveAuctionKicked_ Time a `Claimable Reserve Auction` was last kicked. * @return lastKickedReserves_ Amount of reserves upon last kick, used to calculate price. * @return totalInterestEarned_ Total interest earned by all lenders in the pool */ function reservesInfo() external view returns ( uint256 liquidationBondEscrowed_, uint256 reserveAuctionUnclaimed_, uint256 reserveAuctionKicked_, uint256 lastKickedReserves_, uint256 totalInterestEarned_ ); /** * @notice Returns the `pledgedCollateral` state variable. * @return The total pledged collateral in the system, in WAD units. */ function pledgedCollateral() external view returns (uint256); /** * @notice Returns the total number of active auctions in pool. * @return totalAuctions_ Number of active auctions. */ function totalAuctionsInPool() external view returns (uint256); /** * @notice Returns the `t0Debt` state variable. * @dev This value should be multiplied by inflator in order to calculate current debt of the pool. * @return The total `t0Debt` in the system, in `WAD` units. */ function totalT0Debt() external view returns (uint256); /** * @notice Returns the `t0DebtInAuction` state variable. * @dev This value should be multiplied by inflator in order to calculate current debt in auction of the pool. * @return The total `t0DebtInAuction` in the system, in `WAD` units. */ function totalT0DebtInAuction() external view returns (uint256); /** * @notice Mapping of addresses that can transfer `LP` to a given lender. * @param lender_ Lender that receives `LP`. * @param transferor_ Transferor that transfers `LP`. * @return True if the transferor is approved by lender. */ function approvedTransferors( address lender_, address transferor_ ) external view returns (bool); } /*********************/ /*** State Structs ***/ /*********************/ /******************/ /*** Pool State ***/ /******************/ /// @dev Struct holding inflator state. struct InflatorState { uint208 inflator; // [WAD] pool's inflator uint48 inflatorUpdate; // [SEC] last time pool's inflator was updated } /// @dev Struct holding pool interest state. struct InterestState { uint208 interestRate; // [WAD] pool's interest rate uint48 interestRateUpdate; // [SEC] last time pool's interest rate was updated (not before 12 hours passed) uint256 debt; // [WAD] previous update's debt uint256 meaningfulDeposit; // [WAD] previous update's meaningfulDeposit uint256 t0Debt2ToCollateral; // [WAD] utilization weight accumulator, tracks debt and collateral relationship accross borrowers uint256 debtCol; // [WAD] previous debt squared to collateral uint256 lupt0Debt; // [WAD] previous LUP * t0 debt } /// @dev Struct holding pool EMAs state. struct EmaState { uint256 debtEma; // [WAD] sample of debt EMA, numerator to MAU calculation uint256 depositEma; // [WAD] sample of meaningful deposit EMA, denominator to MAU calculation uint256 debtColEma; // [WAD] debt squared to collateral EMA, numerator to TU calculation uint256 lupt0DebtEma; // [WAD] EMA of LUP * t0 debt, denominator to TU calculation uint256 emaUpdate; // [SEC] last time pool's EMAs were updated } /// @dev Struct holding pool balances state. struct PoolBalancesState { uint256 pledgedCollateral; // [WAD] total collateral pledged in pool uint256 t0DebtInAuction; // [WAD] Total debt in auction used to restrict LPB holder from withdrawing uint256 t0Debt; // [WAD] Pool debt as if the whole amount was incurred upon the first loan } /// @dev Struct holding pool params (in memory only). struct PoolState { uint8 poolType; // pool type, can be ERC20 or ERC721 uint256 t0Debt; // [WAD] t0 debt in pool uint256 t0DebtInAuction; // [WAD] t0 debt in auction within pool uint256 debt; // [WAD] total debt in pool, accrued in current block uint256 collateral; // [WAD] total collateral pledged in pool uint256 inflator; // [WAD] current pool inflator bool isNewInterestAccrued; // true if new interest already accrued in current block uint256 rate; // [WAD] pool's current interest rate uint256 quoteTokenScale; // [WAD] quote token scale of the pool. Same as quote token dust. } /*********************/ /*** Buckets State ***/ /*********************/ /// @dev Struct holding lender state. struct Lender { uint256 lps; // [WAD] Lender LP accumulator uint256 depositTime; // timestamp of last deposit } /// @dev Struct holding bucket state. struct Bucket { uint256 lps; // [WAD] Bucket LP accumulator uint256 collateral; // [WAD] Available collateral tokens deposited in the bucket uint256 bankruptcyTime; // Timestamp when bucket become insolvent, 0 if healthy mapping(address => Lender) lenders; // lender address to Lender struct mapping } /**********************/ /*** Deposits State ***/ /**********************/ /// @dev Struct holding deposits (Fenwick) values and scaling. struct DepositsState { uint256[8193] values; // Array of values in the FenwickTree. uint256[8193] scaling; // Array of values which scale (multiply) the FenwickTree accross indexes. } /*******************/ /*** Loans State ***/ /*******************/ /// @dev Struct holding loans state. struct LoansState { Loan[] loans; mapping (address => uint) indices; // borrower address => loan index mapping mapping (address => Borrower) borrowers; // borrower address => Borrower struct mapping } /// @dev Struct holding loan state. struct Loan { address borrower; // borrower address uint96 t0DebtToCollateral; // [WAD] Borrower t0 debt to collateral. } /// @dev Struct holding borrower state. struct Borrower { uint256 t0Debt; // [WAD] Borrower debt time-adjusted as if it was incurred upon first loan of pool. uint256 collateral; // [WAD] Collateral deposited by borrower. uint256 npTpRatio; // [WAD] Np to Tp ratio at the time of last borrow or pull collateral. } /**********************/ /*** Auctions State ***/ /**********************/ /// @dev Struct holding pool auctions state. struct AuctionsState { uint96 noOfAuctions; // total number of auctions in pool address head; // first address in auction queue address tail; // last address in auction queue uint256 totalBondEscrowed; // [WAD] total amount of quote token posted as auction kick bonds mapping(address => Liquidation) liquidations; // mapping of borrower address and auction details mapping(address => Kicker) kickers; // mapping of kicker address and kicker balances } /// @dev Struct holding liquidation state. struct Liquidation { address kicker; // address that initiated liquidation uint96 bondFactor; // [WAD] bond factor used to start liquidation uint96 kickTime; // timestamp when liquidation was started address prev; // previous liquidated borrower in auctions queue uint96 referencePrice; // [WAD] used to calculate auction start price address next; // next liquidated borrower in auctions queue uint160 bondSize; // [WAD] liquidation bond size uint96 neutralPrice; // [WAD] Neutral Price when liquidation was started uint256 debtToCollateral; // [WAD] Borrower debt to collateral, which is used in BPF for kicker's reward calculation uint256 t0ReserveSettleAmount; // [WAD] Amount of t0Debt that could be settled via reserves in this auction } /// @dev Struct holding kicker state. struct Kicker { uint256 claimable; // [WAD] kicker's claimable balance uint256 locked; // [WAD] kicker's balance of tokens locked in auction bonds } /******************************/ /*** Reserve Auctions State ***/ /******************************/ /// @dev Struct holding reserve auction state. struct ReserveAuctionState { uint256 kicked; // Time a Claimable Reserve Auction was last kicked. uint256 lastKickedReserves; // [WAD] Amount of reserves upon last kick, used to calculate price. uint256 unclaimed; // [WAD] Amount of claimable reserves which has not been taken in the Claimable Reserve Auction. uint256 latestBurnEventEpoch; // Latest burn event epoch. uint256 totalAjnaBurned; // [WAD] Total ajna burned in the pool. uint256 totalInterestEarned; // [WAD] Total interest earned by all lenders in the pool. mapping (uint256 => BurnEvent) burnEvents; // Mapping burnEventEpoch => BurnEvent. } /// @dev Struct holding burn event state. struct BurnEvent { uint256 timestamp; // time at which the burn event occured uint256 totalInterest; // [WAD] current pool interest accumulator `PoolCommons.accrueInterest().newInterest` uint256 totalBurned; // [WAD] burn amount accumulator }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Pool } from './Pool.sol'; import { PoolCommons } from '../libraries/external/PoolCommons.sol'; import { IERC3156FlashBorrower } from '../interfaces/pool/IERC3156FlashBorrower.sol'; /** * @title Flashloanable Pool Contract * @notice Pool contract with `IERC3156` flashloans capabilities. * @notice No fee is charged for taking flashloans from pool. * @notice Flashloans can be taking in `ERC20` quote and `ERC20` collateral tokens. */ abstract contract FlashloanablePool is Pool { using SafeERC20 for IERC20; /** * @notice Called by flashloan borrowers to borrow liquidity which must be repaid in the same transaction. * @param receiver_ Address of the contract which implements the appropriate interface to receive tokens. * @param token_ Address of the `ERC20` token caller wants to borrow. * @param amount_ The denormalized amount (dependent upon token precision) of tokens to borrow. * @param data_ User-defined calldata passed to the receiver. * @return `True` if flashloan was successful. */ function flashLoan( IERC3156FlashBorrower receiver_, address token_, uint256 amount_, bytes calldata data_ ) external virtual override nonReentrant returns (bool) { if (!_isFlashloanSupported(token_)) revert FlashloanUnavailableForToken(); PoolCommons.flashLoan(receiver_, token_, amount_, data_); // if flashLoan call didn't revert then flashloan was successful return true; } /** * @notice Returns `0`, as no fee is charged for flashloans. */ function flashFee( address token_, uint256 ) external virtual view override returns (uint256) { if (!_isFlashloanSupported(token_)) revert FlashloanUnavailableForToken(); return 0; } /** * @notice Returns the amount of tokens available to be lent. * @param token_ Address of the `ERC20` token to be lent. * @return maxLoan_ The amount of `token_` that can be lent. */ function maxFlashLoan( address token_ ) external virtual view override returns (uint256 maxLoan_) { if (_isFlashloanSupported(token_)) maxLoan_ = IERC20(token_).balanceOf(address(this)); } /** * @notice Returns `true` if pool allows flashloans for given token address, `false` otherwise. * @dev Allows flashloans for quote token, overriden in pool implementation to allow flashloans for other tokens. * @param token_ Address of the `ERC20` token to be lent. * @return `True` if token can be flashloaned, `false` otherwise. */ function _isFlashloanSupported( address token_ ) internal virtual view returns (bool) { return token_ == _getArgAddress(QUOTE_ADDRESS); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { PoolType } from '../../interfaces/pool/IPool.sol'; import { InflatorState, PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Maths } from '../internal/Maths.sol'; error BucketIndexOutOfBounds(); error BucketPriceOutOfBounds(); /*************************/ /*** Price Conversions ***/ /*************************/ /// @dev constant price indices defining the min and max of the potential price range int256 constant MAX_BUCKET_INDEX = 4_156; int256 constant MIN_BUCKET_INDEX = -3_232; uint256 constant MAX_FENWICK_INDEX = 7_388; uint256 constant MIN_PRICE = 99_836_282_890; uint256 constant MAX_PRICE = 1_004_968_987.606512354182109771 * 1e18; uint256 constant MAX_INFLATED_PRICE = 50_248_449_380.325617709105488550 * 1e18; // 50 * MAX_PRICE /// @dev deposit buffer (extra margin) used for calculating reserves uint256 constant DEPOSIT_BUFFER = 1.000000001 * 1e18; /// @dev step amounts in basis points. This is a constant across pools at `0.005`, achieved by dividing `WAD` by `10,000` int256 constant FLOAT_STEP_INT = 1.005 * 1e18; /// @dev collateralization factor used to calculate borrrower HTP/TP/collateralization. uint256 constant COLLATERALIZATION_FACTOR = 1.04 * 1e18; /** * @notice Calculates the price (`WAD` precision) for a given `Fenwick` index. * @dev Reverts with `BucketIndexOutOfBounds` if index exceeds maximum constant. * @dev Uses fixed-point math to get around lack of floating point numbers in `EVM`. * @dev Fenwick index is converted to bucket index. * @dev Fenwick index to bucket index conversion: * @dev `1.00` : bucket index `0`, fenwick index `4156`: `7388-4156-3232=0`. * @dev `MAX_PRICE` : bucket index `4156`, fenwick index `0`: `7388-0-3232=4156`. * @dev `MIN_PRICE` : bucket index - `3232`, fenwick index `7388`: `7388-7388-3232=-3232`. * @dev `V1`: `price = MIN_PRICE + (FLOAT_STEP * index)` * @dev `V2`: `price = MAX_PRICE * (FLOAT_STEP ** (abs(int256(index - MAX_PRICE_INDEX))));` * @dev `V3 (final)`: `x^y = 2^(y*log_2(x))` */ function _priceAt( uint256 index_ ) pure returns (uint256) { // Lowest Fenwick index is highest price, so invert the index and offset by highest bucket index. int256 bucketIndex = MAX_BUCKET_INDEX - int256(index_); if (bucketIndex < MIN_BUCKET_INDEX || bucketIndex > MAX_BUCKET_INDEX) revert BucketIndexOutOfBounds(); return uint256( PRBMathSD59x18.exp2( PRBMathSD59x18.mul( PRBMathSD59x18.fromInt(bucketIndex), PRBMathSD59x18.log2(FLOAT_STEP_INT) ) ) ); } /** * @notice Calculates the Fenwick index for a given price. * @dev Reverts with `BucketPriceOutOfBounds` if price exceeds maximum constant. * @dev Price expected to be inputted as a `WAD` (`18` decimal). * @dev `V1`: `bucket index = (price - MIN_PRICE) / FLOAT_STEP` * @dev `V2`: `bucket index = (log(FLOAT_STEP) * price) / MAX_PRICE` * @dev `V3 (final)`: `bucket index = log_2(price) / log_2(FLOAT_STEP)` * @dev `Fenwick index = 7388 - bucket index + 3232` */ function _indexOf( uint256 price_ ) pure returns (uint256) { if (price_ < MIN_PRICE || price_ > MAX_PRICE) revert BucketPriceOutOfBounds(); int256 index = PRBMathSD59x18.div( PRBMathSD59x18.log2(int256(price_)), PRBMathSD59x18.log2(FLOAT_STEP_INT) ); int256 ceilIndex = PRBMathSD59x18.ceil(index); if (index < 0 && ceilIndex - index > 0.5 * 1e18) { return uint256(4157 - PRBMathSD59x18.toInt(ceilIndex)); } return uint256(4156 - PRBMathSD59x18.toInt(ceilIndex)); } /**********************/ /*** Pool Utilities ***/ /**********************/ /** * @notice Calculates the minimum debt amount that can be borrowed or can remain in a loan in pool. * @param debt_ The debt amount to calculate minimum debt amount for. * @param loansCount_ The number of loans in pool. * @return minDebtAmount_ Minimum debt amount value of the pool. */ function _minDebtAmount( uint256 debt_, uint256 loansCount_ ) pure returns (uint256 minDebtAmount_) { if (loansCount_ != 0) { minDebtAmount_ = Maths.wdiv(Maths.wdiv(debt_, Maths.wad(loansCount_)), 10**19); } } /** * @notice Calculates origination fee for a given interest rate. * @notice Calculated as greater of the current annualized interest rate divided by `52` (one week of interest) or `5` bps. * @param interestRate_ The current interest rate. * @return Fee rate based upon the given interest rate. */ function _borrowFeeRate( uint256 interestRate_ ) pure returns (uint256) { // greater of the current annualized interest rate divided by 52 (one week of interest) or 5 bps return Maths.max(Maths.wdiv(interestRate_, 52 * 1e18), 0.0005 * 1e18); } /** * @notice Calculates the unutilized deposit fee, charged to lenders who deposit below the `LUP`. * @param interestRate_ The current interest rate. * @return Fee rate based upon the given interest rate */ function _depositFeeRate( uint256 interestRate_ ) pure returns (uint256) { // current annualized rate divided by 365 * 3 (8 hours of interest) return Maths.wdiv(interestRate_, 365 * 3e18); } /** * @notice Determines how the inflator state should be updated * @param poolState_ State of the pool after updateInterestState was called. * @param inflatorState_ Old inflator state. * @return newInflator_ New inflator value. * @return updateTimestamp_ `True` if timestamp of last update should be updated. */ function _determineInflatorState( PoolState memory poolState_, InflatorState memory inflatorState_ ) view returns (uint208 newInflator_, bool updateTimestamp_) { newInflator_ = inflatorState_.inflator; // update pool inflator if (poolState_.isNewInterestAccrued) { newInflator_ = SafeCast.toUint208(poolState_.inflator); updateTimestamp_ = true; // if the debt in the current pool state is 0, also update the inflator and inflatorUpdate fields in inflatorState // slither-disable-next-line incorrect-equality } else if (poolState_.debt == 0) { newInflator_ = SafeCast.toUint208(Maths.WAD); updateTimestamp_ = true; // if the first loan has just been drawn, update the inflator timestamp // slither-disable-next-line incorrect-equality } else if (inflatorState_.inflator == Maths.WAD && inflatorState_.inflatorUpdate != block.timestamp){ updateTimestamp_ = true; } } /** * @notice Calculates `HTP` price. * @param maxT0DebtToCollateral_ Max t0 debt to collateral in pool. * @param inflator_ Pool's inflator. */ function _htp( uint256 maxT0DebtToCollateral_, uint256 inflator_ ) pure returns (uint256) { return Maths.wmul( Maths.wmul(maxT0DebtToCollateral_, inflator_), COLLATERALIZATION_FACTOR ); } /** * @notice Calculates debt-weighted average threshold price. * @param t0Debt_ Pool debt owed by borrowers in `t0` terms. * @param inflator_ Pool's borrower inflator. * @param t0Debt2ToCollateral_ `t0-debt-squared-to-collateral` accumulator. */ function _dwatp( uint256 t0Debt_, uint256 inflator_, uint256 t0Debt2ToCollateral_ ) pure returns (uint256) { return t0Debt_ == 0 ? 0 : Maths.wdiv( Maths.wmul( Maths.wmul(inflator_, t0Debt2ToCollateral_), COLLATERALIZATION_FACTOR ), t0Debt_ ); } /** * @notice Collateralization calculation. * @param debt_ Debt to calculate collateralization for. * @param collateral_ Collateral to calculate collateralization for. * @param price_ Price to calculate collateralization for. * @param type_ Type of the pool. * @return `True` if value of collateral exceeds or equals debt. */ function _isCollateralized( uint256 debt_, uint256 collateral_, uint256 price_, uint8 type_ ) pure returns (bool) { // `False` if LUP = MIN_PRICE unless there is no debt if (price_ == MIN_PRICE && debt_ != 0) return false; // Use collateral floor for NFT pools if (type_ == uint8(PoolType.ERC721)) { //slither-disable-next-line divide-before-multiply collateral_ = (collateral_ / Maths.WAD) * Maths.WAD; // use collateral floor } return Maths.wmul(collateral_, price_) >= Maths.wmul(COLLATERALIZATION_FACTOR, debt_); } /** * @notice Price precision adjustment used in calculating collateral dust for a bucket. * To ensure the accuracy of the exchange rate calculation, buckets with smaller prices require * larger minimum amounts of collateral. This formula imposes a lower bound independent of token scale. * @param bucketIndex_ Index of the bucket, or `0` for encumbered collateral with no bucket affinity. * @return pricePrecisionAdjustment_ Unscaled integer of the minimum number of decimal places the dust limit requires. */ function _getCollateralDustPricePrecisionAdjustment( uint256 bucketIndex_ ) pure returns (uint256 pricePrecisionAdjustment_) { // conditional is a gas optimization if (bucketIndex_ > 3900) { int256 bucketOffset = int256(bucketIndex_ - 3900); int256 result = PRBMathSD59x18.sqrt(PRBMathSD59x18.div(bucketOffset * 1e18, int256(36 * 1e18))); pricePrecisionAdjustment_ = uint256(result / 1e18); } } /** * @notice Returns the amount of collateral calculated for the given amount of `LP`. * @dev The value returned is capped at collateral amount available in bucket. * @param bucketCollateral_ Amount of collateral in bucket. * @param bucketLP_ Amount of `LP` in bucket. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param lenderLPBalance_ The amount of `LP` to calculate collateral for. * @param bucketPrice_ Bucket's price. * @return collateralAmount_ Amount of collateral calculated for the given `LP `amount. */ function _lpToCollateral( uint256 bucketCollateral_, uint256 bucketLP_, uint256 deposit_, uint256 lenderLPBalance_, uint256 bucketPrice_ ) pure returns (uint256 collateralAmount_) { collateralAmount_ = Buckets.lpToCollateral( bucketCollateral_, bucketLP_, deposit_, lenderLPBalance_, bucketPrice_, Math.Rounding.Down ); if (collateralAmount_ > bucketCollateral_) { // user is owed more collateral than is available in the bucket collateralAmount_ = bucketCollateral_; } } /** * @notice Returns the amount of quote tokens calculated for the given amount of `LP`. * @dev The value returned is capped at available bucket deposit. * @param bucketLP_ Amount of `LP` in bucket. * @param bucketCollateral_ Amount of collateral in bucket. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param lenderLPBalance_ The amount of `LP` to calculate quote token amount for. * @param bucketPrice_ Bucket's price. * @return quoteTokenAmount_ Amount of quote tokens calculated for the given `LP` amount, capped at available bucket deposit. */ function _lpToQuoteToken( uint256 bucketLP_, uint256 bucketCollateral_, uint256 deposit_, uint256 lenderLPBalance_, uint256 bucketPrice_ ) pure returns (uint256 quoteTokenAmount_) { quoteTokenAmount_ = Buckets.lpToQuoteTokens( bucketCollateral_, bucketLP_, deposit_, lenderLPBalance_, bucketPrice_, Math.Rounding.Down ); if (quoteTokenAmount_ > deposit_) quoteTokenAmount_ = deposit_; } /** * @notice Rounds a token amount down to the minimum amount permissible by the token scale. * @param amount_ Value to be rounded. * @param tokenScale_ Scale of the token, presented as a power of `10`. * @return scaledAmount_ Rounded value. */ function _roundToScale( uint256 amount_, uint256 tokenScale_ ) pure returns (uint256 scaledAmount_) { scaledAmount_ = (amount_ / tokenScale_) * tokenScale_; } /** * @notice Rounds a token amount up to the next amount permissible by the token scale. * @param amount_ Value to be rounded. * @param tokenScale_ Scale of the token, presented as a power of `10`. * @return scaledAmount_ Rounded value. */ function _roundUpToScale( uint256 amount_, uint256 tokenScale_ ) pure returns (uint256 scaledAmount_) { if (amount_ % tokenScale_ == 0) scaledAmount_ = amount_; else scaledAmount_ = _roundToScale(amount_, tokenScale_) + tokenScale_; } /*********************************/ /*** Reserve Auction Utilities ***/ /*********************************/ uint256 constant MINUTE_HALF_LIFE = 0.988514020352896135_356867505 * 1e27; // 0.5^(1/60) /** * @notice Calculates claimable reserves within the pool. * @dev Claimable reserve auctions and escrowed auction bonds are guaranteed by the pool. * @param debt_ Pool's debt. * @param poolSize_ Pool's deposit size. * @param totalBondEscrowed_ Total bond escrowed. * @param reserveAuctionUnclaimed_ Pool's unclaimed reserve auction. * @param quoteTokenBalance_ Pool's quote token balance. * @return claimable_ Calculated pool reserves. */ function _claimableReserves( uint256 debt_, uint256 poolSize_, uint256 totalBondEscrowed_, uint256 reserveAuctionUnclaimed_, uint256 quoteTokenBalance_ ) pure returns (uint256 claimable_) { uint256 guaranteedFunds = totalBondEscrowed_ + reserveAuctionUnclaimed_; // calculate claimable reserves if there's quote token excess if (quoteTokenBalance_ > guaranteedFunds) { claimable_ = debt_ + quoteTokenBalance_; claimable_ -= Maths.min( claimable_, // require 1.0 + 1e-9 deposit buffer (extra margin) for deposits Maths.wmul(DEPOSIT_BUFFER, poolSize_) + guaranteedFunds ); // incremental claimable reserve should not exceed excess quote in pool claimable_ = Maths.min( claimable_, quoteTokenBalance_ - guaranteedFunds ); } } /** * @notice Calculates reserves auction price. * @param reserveAuctionKicked_ Time when reserve auction was started (kicked). * @param lastKickedReserves_ Reserves to be auctioned when started (kicked). * @return price_ Calculated auction price. */ function _reserveAuctionPrice( uint256 reserveAuctionKicked_, uint256 lastKickedReserves_ ) view returns (uint256 price_) { if (reserveAuctionKicked_ != 0) { uint256 secondsElapsed = block.timestamp - reserveAuctionKicked_; uint256 hoursComponent = 1e27 >> secondsElapsed / 3600; uint256 minutesComponent = Maths.rpow(MINUTE_HALF_LIFE, secondsElapsed % 3600 / 60); uint256 initialPrice = lastKickedReserves_ == 0 ? 0 : Maths.wdiv(1_000_000_000 * 1e18, lastKickedReserves_); price_ = initialPrice * Maths.rmul(hoursComponent, minutesComponent) / 1e27; } } /*************************/ /*** Auction Utilities ***/ /*************************/ /// @dev min bond factor. uint256 constant MIN_BOND_FACTOR = 0.005 * 1e18; /// @dev max bond factor. uint256 constant MAX_BOND_FACTOR = 0.03 * 1e18; /** * @notice Calculates auction price. * @param referencePrice_ Recorded at kick, used to calculate start price. * @param kickTime_ Time when auction was kicked. * @return price_ Calculated auction price. */ function _auctionPrice( uint256 referencePrice_, uint256 kickTime_ ) view returns (uint256 price_) { uint256 elapsedMinutes = Maths.wdiv((block.timestamp - kickTime_) * 1e18, 1 minutes * 1e18); int256 timeAdjustment; if (elapsedMinutes < 120 * 1e18) { timeAdjustment = PRBMathSD59x18.mul(-1 * 1e18, int256(elapsedMinutes / 20)); price_ = 256 * Maths.wmul(referencePrice_, uint256(PRBMathSD59x18.exp2(timeAdjustment))); } else if (elapsedMinutes < 840 * 1e18) { timeAdjustment = PRBMathSD59x18.mul(-1 * 1e18, int256((elapsedMinutes - 120 * 1e18) / 120)); price_ = 4 * Maths.wmul(referencePrice_, uint256(PRBMathSD59x18.exp2(timeAdjustment))); } else { timeAdjustment = PRBMathSD59x18.mul(-1 * 1e18, int256((elapsedMinutes - 840 * 1e18) / 60)); price_ = Maths.wmul(referencePrice_, uint256(PRBMathSD59x18.exp2(timeAdjustment))) / 16; } } /** * @notice Calculates bond penalty factor. * @dev Called in kick and take. * @param debtToCollateral_ Borrower debt to collateral at time of kick. * @param neutralPrice_ `NP` of auction. * @param bondFactor_ Factor used to determine bondSize. * @param auctionPrice_ Auction price at the time of call or, for bucket takes, bucket price. * @return bpf_ Factor used in determining bond `reward` (positive) or `penalty` (negative). */ function _bpf( uint256 debtToCollateral_, uint256 neutralPrice_, uint256 bondFactor_, uint256 auctionPrice_ ) pure returns (int256) { int256 sign; if (debtToCollateral_ < neutralPrice_) { // BPF = BondFactor * min(1, max(-1, (neutralPrice - price) / (neutralPrice - debtToCollateral))) sign = Maths.minInt( 1e18, Maths.maxInt( -1 * 1e18, PRBMathSD59x18.div( int256(neutralPrice_) - int256(auctionPrice_), int256(neutralPrice_) - int256(debtToCollateral_) ) ) ); } else { int256 val = int256(neutralPrice_) - int256(auctionPrice_); if (val < 0 ) sign = -1e18; else if (val != 0) sign = 1e18; } return PRBMathSD59x18.mul(int256(bondFactor_), sign); } /** * @notice Calculates bond parameters of an auction. * @param borrowerDebt_ Borrower's debt before entering in liquidation. * @param npTpRatio_ Borrower's Np to Tp ratio */ function _bondParams( uint256 borrowerDebt_, uint256 npTpRatio_ ) pure returns (uint256 bondFactor_, uint256 bondSize_) { // bondFactor = max(min(MAX_BOND_FACTOR, (NP/TP_ratio - 1) / 10), MIN_BOND_FACTOR) bondFactor_ = Maths.max( Maths.min( MAX_BOND_FACTOR, (npTpRatio_ - 1e18) / 10 ), MIN_BOND_FACTOR ); bondSize_ = Maths.wmul(bondFactor_, borrowerDebt_); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { AuctionsState, Borrower, DepositsState, LoansState, PoolBalancesState } from '../../interfaces/pool/commons/IPoolState.sol'; import { _minDebtAmount, _priceAt, _auctionPrice } from './PoolHelper.sol'; import { Loans } from '../internal/Loans.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Maths } from '../internal/Maths.sol'; // See `IPoolErrors` for descriptions error AuctionNotCleared(); error AuctionActive(); error AmountLTMinDebt(); error DustAmountNotExceeded(); error LimitIndexExceeded(); error RemoveDepositLockedByAuctionDebt(); error TransactionExpired(); error AddAboveAuctionPrice(); /** * @notice Called by `LP` removal functions assess whether or not `LP` is locked. * @dev Reverts with `RemoveDepositLockedByAuctionDebt` if debt locked. * @param t0DebtInAuction_ Pool's t0 debt currently in auction. * @param index_ The deposit index from which `LP` is attempting to be removed. * @param inflator_ The pool inflator used to properly assess t0 debt in auctions. */ function _revertIfAuctionDebtLocked( DepositsState storage deposits_, uint256 t0DebtInAuction_, uint256 index_, uint256 inflator_ ) view { if (t0DebtInAuction_ != 0 ) { // deposit in buckets within liquidation debt from the top-of-book down are frozen. if (index_ <= Deposits.findIndexOfSum(deposits_, Maths.wmul(t0DebtInAuction_, inflator_))) revert RemoveDepositLockedByAuctionDebt(); } } /** * @notice Check if head auction is clearable (auction is kicked and `72` hours passed since kick time or auction still has debt but no remaining collateral). * @dev Reverts with `AuctionNotCleared` if auction is clearable. */ function _revertIfAuctionClearable( AuctionsState storage auctions_, LoansState storage loans_ ) view { address head = auctions_.head; uint256 kickTime = auctions_.liquidations[head].kickTime; if (kickTime != 0) { if (block.timestamp - kickTime > 72 hours) revert AuctionNotCleared(); Borrower storage borrower = loans_.borrowers[head]; if (borrower.t0Debt != 0 && borrower.collateral == 0) revert AuctionNotCleared(); } } /** * @notice Check if provided price is at or above index limit provided by borrower. * @notice Prevents stale transactions and certain `MEV` manipulations. * @dev Reverts with `LimitIndexExceeded` if index limit provided exceeded. * @param newPrice_ New price to be compared with given limit price (can be `LUP`, `NP`). * @param limitIndex_ Limit price index provided by user creating the transaction. */ function _revertIfPriceDroppedBelowLimit( uint256 newPrice_, uint256 limitIndex_ ) pure { if (newPrice_ < _priceAt(limitIndex_)) revert LimitIndexExceeded(); } /** * @notice Check if provided price is above current auction price. * @notice Prevents manipulative deposits and arbTakes. * @dev Reverts with `AddAboveAuctionPrice` if price is above head of auction queue. * @param auctions_ Auctions data. * @param index_ Identifies bucket price to be compared with current auction price. */ function _revertIfAuctionPriceBelow( AuctionsState storage auctions_, uint256 index_ ) view { address head = auctions_.head; if (head != address(0)) { uint256 auctionPrice = _auctionPrice(auctions_.liquidations[head].referencePrice, auctions_.liquidations[head].kickTime); if (_priceAt(index_) >= auctionPrice) revert AddAboveAuctionPrice(); } } /** * @notice Check if there are still active / non settled auctions in pool. * @notice Prevents kicking reserves auctions until all pending auctions are fully settled. * @dev Reverts with `AuctionActive`. * @param auctions_ Auctions data. */ function _revertIfActiveAuctions( AuctionsState storage auctions_ ) view { if (auctions_.noOfAuctions != 0) revert AuctionActive(); } /** * @notice Check if expiration provided by user has met or exceeded current block height timestamp. * @notice Prevents stale transactions interacting with the pool at potentially unfavorable prices. * @dev Reverts with `TransactionExpired` if expired. * @param expiry_ Expiration provided by user when creating the transaction. */ function _revertAfterExpiry( uint256 expiry_ ) view { if (block.timestamp > expiry_) revert TransactionExpired(); } /** * @notice Called when borrower debt changes, ensuring minimum debt rules are honored. * @dev Reverts with `DustAmountNotExceeded` if under dust amount or with `AmountLTMinDebt` if amount under min debt value. * @param loans_ Loans heap, used to determine loan count. * @param poolDebt_ Total pool debt, used to calculate average debt. * @param borrowerDebt_ New debt for the borrower, assuming the current transaction succeeds. * @param quoteDust_ Smallest amount of quote token when can be transferred, determined by token scale. */ function _revertOnMinDebt( LoansState storage loans_, uint256 poolDebt_, uint256 borrowerDebt_, uint256 quoteDust_ ) view { if (borrowerDebt_ != 0) { if (borrowerDebt_ < quoteDust_) revert DustAmountNotExceeded(); uint256 loansCount = Loans.noOfLoans(loans_); if (loansCount >= 10) if (borrowerDebt_ < _minDebtAmount(poolDebt_, loansCount)) revert AmountLTMinDebt(); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { AuctionsState, Borrower, DepositsState, Loan, LoansState } from '../../interfaces/pool/commons/IPoolState.sol'; import { _priceAt } from '../helpers/PoolHelper.sol'; import { Deposits } from './Deposits.sol'; import { Maths } from './Maths.sol'; /** @title Loans library @notice Internal library containing common logic for loans management. @dev The `Loans` heap is a `Max Heap` data structure (complete binary tree), the root node is the loan with the highest t0 threshold price (`TP`) at a given time. The heap is represented as an array, where the first element is a dummy element (`Loan(address(0), 0)`) and the first value of the heap starts at index `1`, `ROOT_INDEX`. The t0 threshold price of a loan's parent is always greater than or equal to the t0 threshold price of the loan. @dev This code was modified from the following source: https://github.com/zmitton/eth-heap/blob/master/contracts/Heap.sol */ library Loans { uint256 constant ROOT_INDEX = 1; /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error ZeroDebtToCollateral(); /***********************/ /*** Initialization ***/ /***********************/ /** * @notice Initializes Loans Max Heap. * @dev Organizes loans so `Highest t0 threshold price` can be retrieved easily. * @param loans_ Holds Loan heap data. */ function init(LoansState storage loans_) internal { loans_.loans.push(Loan(address(0), 0)); } /***********************************/ /*** Loans Management Functions ***/ /***********************************/ /** * @notice Updates a loan: updates heap (`upsert` if `TP` not `0`, `remove` otherwise) and borrower balance. * @dev === Write state === * @dev - `_upsert`: * @dev insert or update loan in `loans` array * @dev - `remove`: * @dev remove loan from `loans` array * @dev - update borrower in `address => borrower` mapping * @param loans_ Holds loans heap data. * @param borrower_ Borrower struct with borrower details. * @param borrowerAddress_ Borrower's address to update. * @param poolRate_ Pool's current rate. * @param inAuction_ Whether the loan is in auction or not. * @param npTpRatioUpdate_ Whether the Np to Tp ratio of borrower should be updated or not. */ function update( LoansState storage loans_, Borrower memory borrower_, address borrowerAddress_, uint256 poolRate_, bool inAuction_, bool npTpRatioUpdate_ ) internal { bool activeBorrower = borrower_.t0Debt != 0 && borrower_.collateral != 0; uint256 t0DebtToCollateral = activeBorrower ? Maths.wdiv(borrower_.t0Debt, borrower_.collateral) : 0; // loan not in auction, update t0 threshold price and position in heap if (!inAuction_ ) { // get the loan id inside the heap uint256 loanId = loans_.indices[borrowerAddress_]; if (activeBorrower) { // revert if t0 threshold price is zero if (t0DebtToCollateral == 0) revert ZeroDebtToCollateral(); // update heap, insert if a new loan, update loan if already in heap _upsert(loans_, borrowerAddress_, loanId, SafeCast.toUint96(t0DebtToCollateral)); // if loan is in heap and borrwer is no longer active (no debt, no collateral) then remove loan from heap } else if (loanId != 0) { remove(loans_, borrowerAddress_, loanId); } } // update Np to Tp ratio of borrower if (npTpRatioUpdate_) { borrower_.npTpRatio = 1e18 + uint256(PRBMathSD59x18.sqrt(int256(poolRate_))) / 2; } // save borrower state loans_.borrowers[borrowerAddress_] = borrower_; } /**************************************/ /*** Loans Heap Internal Functions ***/ /**************************************/ /** * @notice Moves a `Loan` up the heap. * @param loans_ Holds loans heap data. * @param loan_ `Loan` to be moved. * @param index_ Index of `Loan` to be moved to. */ function _bubbleUp(LoansState storage loans_, Loan memory loan_, uint index_) private { uint256 count = loans_.loans.length; if (index_ == ROOT_INDEX || loan_.t0DebtToCollateral <= loans_.loans[index_ / 2].t0DebtToCollateral){ _insert(loans_, loan_, index_, count); } else { _insert(loans_, loans_.loans[index_ / 2], index_, count); _bubbleUp(loans_, loan_, index_ / 2); } } /** * @notice Moves a `Loan` down the heap. * @param loans_ Holds loans heap data. * @param loan_ `Loan` to be moved. * @param index_ Index of `Loan` to be moved to. */ function _bubbleDown(LoansState storage loans_, Loan memory loan_, uint index_) private { // Left child index. uint cIndex = index_ * 2; uint256 count = loans_.loans.length; if (count <= cIndex) { _insert(loans_, loan_, index_, count); } else { Loan memory largestChild = loans_.loans[cIndex]; if (count > cIndex + 1 && loans_.loans[cIndex + 1].t0DebtToCollateral > largestChild.t0DebtToCollateral) { largestChild = loans_.loans[++cIndex]; } if (largestChild.t0DebtToCollateral <= loan_.t0DebtToCollateral) { _insert(loans_, loan_, index_, count); } else { _insert(loans_, largestChild, index_, count); _bubbleDown(loans_, loan_, cIndex); } } } /** * @notice Inserts a `Loan` in the heap. * @param loans_ Holds loans heap data. * @param loan_ `Loan` to be inserted. * @param index_ Index of `Loan` to be inserted at. */ function _insert(LoansState storage loans_, Loan memory loan_, uint index_, uint256 count_) private { if (index_ == count_) loans_.loans.push(loan_); else loans_.loans[index_] = loan_; loans_.indices[loan_.borrower] = index_; } /** * @notice Removes `Loan` from heap given borrower address. * @param loans_ Holds loans heap data. * @param borrower_ Borrower address whose `Loan` is being updated or inserted. * @param index_ Index of `Loan` to be removed. */ function remove(LoansState storage loans_, address borrower_, uint256 index_) internal { delete loans_.indices[borrower_]; uint256 tailIndex = loans_.loans.length - 1; if (index_ == tailIndex) loans_.loans.pop(); // we're removing the tail, pop without sorting else { Loan memory tail = loans_.loans[tailIndex]; loans_.loans.pop(); // remove tail loan _bubbleUp(loans_, tail, index_); _bubbleDown(loans_, loans_.loans[index_], index_); } } /** * @notice Performs an insert or an update dependent on borrowers existance. * @param loans_ Holds loans heap data. * @param borrower_ Borrower address that is being updated or inserted. * @param index_ Index of `Loan` to be upserted. * @param t0DebtToCollateral_ Borrower t0 debt to collateral that is updated or inserted. */ function _upsert( LoansState storage loans_, address borrower_, uint256 index_, uint96 t0DebtToCollateral_ ) internal { // Loan exists, update in place. if (index_ != 0) { Loan memory currentLoan = loans_.loans[index_]; if (currentLoan.t0DebtToCollateral > t0DebtToCollateral_) { currentLoan.t0DebtToCollateral = t0DebtToCollateral_; _bubbleDown(loans_, currentLoan, index_); } else { currentLoan.t0DebtToCollateral = t0DebtToCollateral_; _bubbleUp(loans_, currentLoan, index_); } // New loan, insert it } else { _bubbleUp(loans_, Loan(borrower_, t0DebtToCollateral_), loans_.loans.length); } } /**********************/ /*** View Functions ***/ /**********************/ /** * @notice Retreives `Loan` by index, `index_`. * @param loans_ Holds loans heap data. * @param index_ Index to retrieve `Loan`. * @return `Loan` struct retrieved by index. */ function getByIndex(LoansState storage loans_, uint256 index_) internal view returns(Loan memory) { return loans_.loans.length > index_ ? loans_.loans[index_] : Loan(address(0), 0); } /** * @notice Retreives `Loan` with the highest t0 threshold price value. * @param loans_ Holds loans heap data. * @return `Max Loan` in the heap. */ function getMax(LoansState storage loans_) internal view returns(Loan memory) { return getByIndex(loans_, ROOT_INDEX); } /** * @notice Returns number of loans in pool. * @param loans_ Holds loans heap data. * @return Number of loans in pool. */ function noOfLoans(LoansState storage loans_) internal view returns (uint256) { return loans_.loans.length - 1; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { DepositsState } from '../../interfaces/pool/commons/IPoolState.sol'; import { _priceAt, MAX_FENWICK_INDEX } from '../helpers/PoolHelper.sol'; import { Maths } from './Maths.sol'; /** @title Deposits library @notice Internal library containing common logic for deposits management. @dev Implemented as `Fenwick Tree` data structure. */ library Deposits { /// @dev Max index supported in the `Fenwick` tree uint256 internal constant SIZE = 8192; /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error InvalidAmount(); /** * @notice Increase a value in the FenwickTree at an index. * @dev Starts at leaf/target and moved up towards root * @dev === Reverts on === * @dev unscaled amount to add is 0 `InvalidAmount()` * @param deposits_ Deposits state struct. * @param index_ The deposit index. * @param unscaledAddAmount_ The unscaled amount to increase deposit by. */ function unscaledAdd( DepositsState storage deposits_, uint256 index_, uint256 unscaledAddAmount_ ) internal { // revert if 0 amount is added. if (unscaledAddAmount_ == 0) revert InvalidAmount(); // price buckets are indexed starting at 0, Fenwick bit logic is more elegant starting at 1 ++index_; // unscaledAddAmount_ is the raw amount to add directly to the value at index_, unaffected by the scale array // For example, to denote an amount of deposit added to the array, we would need to call unscaledAdd with // (deposit amount) / scale(index). There are two reasons for this: // 1- scale(index) is often already known in the context of where unscaledAdd(..) is called, and we want to avoid // redundant iterations through the Fenwick tree. // 2- We often need to precisely change the value in the tree, avoiding the rounding that dividing by scale(index). // This is more relevant to unscaledRemove(...), where we need to ensure the value is precisely set to 0, but we // also prefer it here for consistency. uint256 value; uint256 scaling; uint256 newValue; while (index_ <= SIZE) { value = deposits_.values[index_]; scaling = deposits_.scaling[index_]; // Compute the new value to be put in location index_ newValue = value + unscaledAddAmount_; // Update unscaledAddAmount to propogate up the Fenwick tree // Note: we can't just multiply addAmount_ by scaling[i_] due to rounding // We need to track the precice change in values[i_] in order to ensure // obliterated indices remain zero after subsequent adding to related indices // if scaling==0, the actual scale value is 1, otherwise it is scaling if (scaling != 0) unscaledAddAmount_ = Maths.wmul(newValue, scaling) - Maths.wmul(value, scaling); deposits_.values[index_] = newValue; // traverse upwards through tree via "update" route index_ += lsb(index_); } } /** * @notice Finds index and sum of first bucket that EXCEEDS the given sum * @dev Used in `LUP` calculation * @param deposits_ Struct for deposits state. * @param targetSum_ The sum to find index for. * @return sumIndex_ Smallest index where prefixsum greater than the sum. * @return sumIndexSum_ Sum at index PRECEDING `sumIndex_`. * @return sumIndexScale_ Scale of bucket PRECEDING `sumIndex_`. */ function findIndexAndSumOfSum( DepositsState storage deposits_, uint256 targetSum_ ) internal view returns (uint256 sumIndex_, uint256 sumIndexSum_, uint256 sumIndexScale_) { // i iterates over bits from MSB to LSB. We check at each stage if the target sum is to the left or right of sumIndex_+i uint256 i = 4096; // 1 << (_numBits - 1) = 1 << (13 - 1) = 4096 uint256 runningScale = Maths.WAD; // We construct the target sumIndex_ bit by bit, from MSB to LSB. lowerIndexSum_ always maintains the sum // up to the current value of sumIndex_ uint256 lowerIndexSum; uint256 curIndex; uint256 value; uint256 scaling; uint256 scaledValue; while (i > 0) { // Consider if the target index is less than or greater than sumIndex_ + i curIndex = sumIndex_ + i; value = deposits_.values[curIndex]; scaling = deposits_.scaling[curIndex]; // Compute sum up to sumIndex_ + i scaledValue = lowerIndexSum + ( scaling != 0 ? Math.mulDiv( runningScale * scaling, value, 1e36 ) : Maths.wmul(runningScale, value) ); if (scaledValue < targetSum_) { // Target value is too small, need to consider increasing sumIndex_ still if (curIndex <= MAX_FENWICK_INDEX) { // sumIndex_+i is in range of Fenwick prices. Target index has this bit set to 1. sumIndex_ = curIndex; lowerIndexSum = scaledValue; } } else { // Target index has this bit set to 0 // scaling == 0 means scale factor == 1, otherwise scale factor == scaling if (scaling != 0) runningScale = Maths.floorWmul(runningScale, scaling); // Current scaledValue is <= targetSum_, it's a candidate value for sumIndexSum_ sumIndexSum_ = scaledValue; sumIndexScale_ = runningScale; } // Shift i to next less significant bit i = i >> 1; } } /** * @notice Finds index of passed sum. Helper function for `findIndexAndSumOfSum`. * @dev Used in `LUP` calculation * @param deposits_ Deposits state struct. * @param sum_ The sum to find index for. * @return sumIndex_ Smallest index where prefixsum greater than the sum. */ function findIndexOfSum( DepositsState storage deposits_, uint256 sum_ ) internal view returns (uint256 sumIndex_) { (sumIndex_,,) = findIndexAndSumOfSum(deposits_, sum_); } /** * @notice Get least significant bit (`LSB`) of integer `i_`. * @dev Used primarily to decrement the binary index in loops, iterating over range parents. * @param i_ The integer with which to return the `LSB`. */ function lsb( uint256 i_ ) internal pure returns (uint256 lsb_) { if (i_ != 0) { // "i & (-i)" lsb_ = i_ & ((i_ ^ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + 1); } } /** * @notice Scale values in the tree from the index provided, upwards. * @dev Starts at passed in node and increments through range parent nodes, and ends at `8192`. * @param deposits_ Deposits state struct. * @param index_ The index to start scaling from. * @param factor_ The factor to scale the values by. */ function mult( DepositsState storage deposits_, uint256 index_, uint256 factor_ ) internal { // price buckets are indexed starting at 0, Fenwick bit logic is more elegant starting at 1 ++index_; uint256 sum; uint256 value; uint256 scaling; uint256 bit = lsb(index_); // Starting with the LSB of index, we iteratively move up towards the MSB of SIZE // Case 1: the bit of index_ is set to 1. In this case, the entire subtree below index_ // is scaled. So, we include factor_ into scaling[index_], and remember in sum how much // we increased the subtree by, so that we can use it in case we encounter 0 bits (below). // Case 2: The bit of index_ is set to 0. In this case, consider the subtree below the node // index_+bit. The subtree below that is not entirely scaled, but it does contain the // subtree what was scaled earlier. Therefore: we need to increment it's stored value // (in sum) which was set in a prior interation in case 1. while (bit <= SIZE) { if ((bit & index_) != 0) { // Case 1 as described above value = deposits_.values[index_]; scaling = deposits_.scaling[index_]; // Calc sum, will only be stored in range parents of starting node, index_ if (scaling != 0) { // Note: we can't just multiply by factor_ - 1 in the following line, as rounding will // cause obliterated indices to have nonzero values. Need to track the actual // precise delta in the value array uint256 scaledFactor = Maths.wmul(factor_, scaling); sum += Maths.wmul(scaledFactor, value) - Maths.wmul(scaling, value); // Apply scaling to all range parents less then starting node, index_ deposits_.scaling[index_] = scaledFactor; } else { // this node's scale factor is 1 sum += Maths.wmul(factor_, value) - value; deposits_.scaling[index_] = factor_; } // Unset the bit in index to continue traversing up the Fenwick tree index_ -= bit; } else { // Case 2 above. superRangeIndex is the index of the node to consider that // contains the sub range that was already scaled in prior iteration uint256 superRangeIndex = index_ + bit; value = (deposits_.values[superRangeIndex] += sum); scaling = deposits_.scaling[superRangeIndex]; // Need to be careful due to rounding to propagate actual changes upwards in tree. // sum is always equal to the actual value we changed deposits_.values[] by if (scaling != 0) sum = Maths.wmul(value, scaling) - Maths.wmul(value - sum, scaling); } // consider next most significant bit bit = bit << 1; } } /** * @notice Get prefix sum of all indexes from provided index downwards. * @dev Starts at tree root and decrements through range parent nodes summing from index `sumIndex_`'s range to index `0`. * @param deposits_ Deposits state struct. * @param sumIndex_ The index to receive the prefix sum. * @param sum_ The prefix sum from current index downwards. */ function prefixSum( DepositsState storage deposits_, uint256 sumIndex_ ) internal view returns (uint256 sum_) { // price buckets are indexed starting at 0, Fenwick bit logic is more elegant starting at 1 ++sumIndex_; uint256 runningScale = Maths.WAD; // Tracks scale(index_) as we move down Fenwick tree uint256 j = SIZE; // bit that iterates from MSB to LSB uint256 index = 0; // build up sumIndex bit by bit // Used to terminate loop. We don't need to consider final 0 bits of sumIndex_ uint256 indexLSB = lsb(sumIndex_); uint256 curIndex; while (j >= indexLSB) { curIndex = index + j; // Skip considering indices outside bounds of Fenwick tree if (curIndex > SIZE) continue; // We are considering whether to include node index + j in the sum or not. Either way, we need to scaling[index + j], // either to increment sum_ or to accumulate in runningScale uint256 scaled = deposits_.scaling[curIndex]; if (sumIndex_ & j != 0) { // node index + j of tree is included in sum uint256 value = deposits_.values[curIndex]; // Accumulate in sum_, recall that scaled==0 means that the scale factor is actually 1 sum_ += scaled != 0 ? Math.mulDiv( runningScale * scaled, value, 1e36 ) : Maths.wmul(runningScale, value); // Build up index bit by bit index = curIndex; // terminate if we've already matched sumIndex_ if (index == sumIndex_) break; } else { // node is not included in sum, but its scale needs to be included for subsequent sums if (scaled != 0) runningScale = Maths.floorWmul(runningScale, scaled); } // shift j to consider next less signficant bit j = j >> 1; } } /** * @notice Decrease a node in the `FenwickTree` at an index. * @dev Starts at leaf/target and moved up towards root. * @dev === Reverts on === * @dev unscaled amount to remove is 0 `InvalidAmount()` * @param deposits_ Deposits state struct. * @param index_ The deposit index. * @param unscaledRemoveAmount_ Unscaled amount to decrease deposit by. */ function unscaledRemove( DepositsState storage deposits_, uint256 index_, uint256 unscaledRemoveAmount_ ) internal { // revert if 0 amount is removed. if (unscaledRemoveAmount_ == 0) revert InvalidAmount(); // price buckets are indexed starting at 0, Fenwick bit logic is more elegant starting at 1 ++index_; // We operate with unscaledRemoveAmount_ here instead of a scaled quantity to avoid duplicate computation of scale factor // (thus redundant iterations through the Fenwick tree), and ALSO so that we can set the value of a given deposit exactly // to 0. while (index_ <= SIZE) { // Decrement deposits_ at index_ for removeAmount, storing new value in value uint256 value = (deposits_.values[index_] -= unscaledRemoveAmount_); uint256 scaling = deposits_.scaling[index_]; // If scale factor != 1, we need to adjust unscaledRemoveAmount by scale factor to adjust values further up in tree // On the line below, it would be tempting to replace this with: // unscaledRemoveAmount_ = Maths.wmul(unscaledRemoveAmount, scaling). This will introduce nonzero values up // the tree due to rounding. It's important to compute the actual change in deposits_.values[index_] // and propogate that upwards. if (scaling != 0) unscaledRemoveAmount_ = Maths.wmul(value + unscaledRemoveAmount_, scaling) - Maths.wmul(value, scaling); // Traverse upward through the "update" path of the Fenwick tree index_ += lsb(index_); } } /** * @notice Scale tree starting from given index. * @dev Starts at leaf/target and moved up towards root. * @param deposits_ Deposits state struct. * @param index_ The deposit index. * @return scaled_ Scaled value. */ function scale( DepositsState storage deposits_, uint256 index_ ) internal view returns (uint256 scaled_) { // price buckets are indexed starting at 0, Fenwick bit logic is more elegant starting at 1 ++index_; // start with scaled_1 = 1 scaled_ = Maths.WAD; while (index_ <= SIZE) { // Traverse up through Fenwick tree via "update" path, accumulating scale factors as we go uint256 scaling = deposits_.scaling[index_]; // scaling==0 means actual scale factor is 1 if (scaling != 0) scaled_ = Maths.wmul(scaled_, scaling); index_ += lsb(index_); } } /** * @notice Returns sum of all deposits. * @param deposits_ Deposits state struct. * @return Sum of all deposits in tree. */ function treeSum( DepositsState storage deposits_ ) internal view returns (uint256) { // In a scaled Fenwick tree, sum is at the root node and never scaled return deposits_.values[SIZE]; } /** * @notice Returns deposit value for a given deposit index. * @param deposits_ Deposits state struct. * @param index_ The deposit index. * @return depositValue_ Value of the deposit. */ function valueAt( DepositsState storage deposits_, uint256 index_ ) internal view returns (uint256 depositValue_) { // Get unscaled value at index and multiply by scale depositValue_ = Maths.wmul(unscaledValueAt(deposits_, index_), scale(deposits_,index_)); } /** * @notice Returns unscaled (deposit without interest) deposit value for a given deposit index. * @param deposits_ Deposits state struct. * @param index_ The deposit index. * @return unscaledDepositValue_ Value of unscaled deposit. */ function unscaledValueAt( DepositsState storage deposits_, uint256 index_ ) internal view returns (uint256 unscaledDepositValue_) { // In a scaled Fenwick tree, sum is at the root node, but needs to be scaled ++index_; uint256 j = 1; // Returns the unscaled value at the node. We consider the unscaled value for two reasons: // 1- If we want to zero out deposit in bucket, we need to subtract the exact unscaled value // 2- We may already have computed the scale factor, so we can avoid duplicate traversal unscaledDepositValue_ = deposits_.values[index_]; uint256 curIndex; uint256 value; uint256 scaling; while (j & index_ == 0) { curIndex = index_ - j; value = deposits_.values[curIndex]; scaling = deposits_.scaling[curIndex]; unscaledDepositValue_ -= scaling != 0 ? Maths.wmul(scaling, value) : value; j = j << 1; } } /** * @notice Returns `LUP` for a given debt value (capped at min bucket price). * @param deposits_ Deposits state struct. * @param debt_ The debt amount to calculate `LUP` for. * @return `LUP` for given debt. */ function getLup( DepositsState storage deposits_, uint256 debt_ ) internal view returns (uint256) { return _priceAt(findIndexOfSum(deposits_, debt_)); } }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.18; /** @title Maths library @notice Internal library containing common maths. */ library Maths { uint256 internal constant WAD = 1e18; uint256 internal constant RAY = 1e27; function wmul(uint256 x, uint256 y) internal pure returns (uint256) { return (x * y + WAD / 2) / WAD; } function floorWmul(uint256 x, uint256 y) internal pure returns (uint256) { return (x * y) / WAD; } function ceilWmul(uint256 x, uint256 y) internal pure returns (uint256) { return (x * y + WAD - 1) / WAD; } function wdiv(uint256 x, uint256 y) internal pure returns (uint256) { return (x * WAD + y / 2) / y; } function floorWdiv(uint256 x, uint256 y) internal pure returns (uint256) { return (x * WAD) / y; } function ceilWdiv(uint256 x, uint256 y) internal pure returns (uint256) { return (x * WAD + y - 1) / y; } function ceilDiv(uint256 x, uint256 y) internal pure returns (uint256) { return (x + y - 1) / y; } function max(uint256 x, uint256 y) internal pure returns (uint256) { return x >= y ? x : y; } function min(uint256 x, uint256 y) internal pure returns (uint256) { return x <= y ? x : y; } function wad(uint256 x) internal pure returns (uint256) { return x * WAD; } function rmul(uint256 x, uint256 y) internal pure returns (uint256) { return (x * y + RAY / 2) / RAY; } function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) { z = n % 2 != 0 ? x : RAY; for (n /= 2; n != 0; n /= 2) { x = rmul(x, x); if (n % 2 != 0) { z = rmul(z, x); } } } /*************************/ /*** Integer Functions ***/ /*************************/ function maxInt(int256 x, int256 y) internal pure returns (int256) { return x >= y ? x : y; } function minInt(int256 x, int256 y) internal pure returns (int256) { return x <= y ? x : y; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { AuctionsState, Borrower, Bucket, DepositsState, LoansState, PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; import { DrawDebtResult, RepayDebtResult } from '../../interfaces/pool/commons/IPoolInternals.sol'; import { _borrowFeeRate, _priceAt, _isCollateralized } from '../helpers/PoolHelper.sol'; import { _revertIfPriceDroppedBelowLimit, _revertOnMinDebt } from '../helpers/RevertsHelper.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Loans } from '../internal/Loans.sol'; import { Maths } from '../internal/Maths.sol'; import { SettlerActions } from './SettlerActions.sol'; /** @title BorrowerActions library @notice External library containing logic for for pool actors: - `Borrowers`: pledge collateral and draw debt; repay debt and pull collateral */ library BorrowerActions { /*************************/ /*** Local Var Structs ***/ /*************************/ /// @dev Struct used for `drawDebt` function local vars. struct DrawDebtLocalVars { bool borrow; // true if borrow action uint256 borrowerDebt; // [WAD] borrower's accrued debt uint256 t0BorrowAmount; // [WAD] t0 amount to borrow uint256 t0DebtChange; // [WAD] additional t0 debt resulted from draw debt action bool pledge; // true if pledge action bool stampNpTpRatio; // true if loan's Np to Tp ratio should be restamped (when drawing debt or pledge settles auction) } /// @dev Struct used for `repayDebt` function local vars. struct RepayDebtLocalVars { uint256 borrowerDebt; // [WAD] borrower's accrued debt bool pull; // true if pull action bool repay; // true if repay action bool stampNpTpRatio; // true if loan's Np to Tp ratio should be restamped (when repay settles auction or pull collateral) uint256 t0RepaidDebt; // [WAD] t0 debt repaid } /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event LoanStamped(address indexed borrowerAddress); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error AuctionActive(); error BorrowerNotSender(); error BorrowerUnderCollateralized(); error InsufficientLiquidity(); error InsufficientCollateral(); error InvalidAmount(); error LimitIndexExceeded(); error NoDebt(); /***************************/ /*** External Functions ***/ /***************************/ /** * @notice See `IERC20PoolBorrowerActions` and `IERC721PoolBorrowerActions` for descriptions * @dev === Write state === * @dev - `Loans.update` (`_upsert`): * @dev insert or update loan in loans array * @dev remove loan from loans array * @dev update borrower in `address => borrower` mapping * @dev === Reverts on === * @dev not enough quote tokens available `InsufficientLiquidity()` * @dev borrower not sender `BorrowerNotSender()` * @dev borrower debt less than pool min debt `AmountLTMinDebt()` * @dev limit price reached `LimitIndexExceeded()` * @dev borrower cannot draw more debt `BorrowerUnderCollateralized()` * @dev borrower cannot be in auction `AuctionActive()` */ function drawDebt( AuctionsState storage auctions_, DepositsState storage deposits_, LoansState storage loans_, PoolState calldata poolState_, uint256 maxAvailable_, address borrowerAddress_, uint256 amountToBorrow_, uint256 limitIndex_, uint256 collateralToPledge_ ) external returns ( DrawDebtResult memory result_ ) { // revert if not enough pool balance to borrow if (amountToBorrow_ > maxAvailable_) revert InsufficientLiquidity(); // revert if borrower is in auction if (_inAuction(auctions_, borrowerAddress_)) revert AuctionActive(); DrawDebtLocalVars memory vars; vars.pledge = collateralToPledge_ != 0; vars.borrow = amountToBorrow_ != 0; // revert if no amount to pledge or borrow if (!vars.pledge && !vars.borrow) revert InvalidAmount(); Borrower memory borrower = loans_.borrowers[borrowerAddress_]; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; result_.t0PoolDebt = poolState_.t0Debt; result_.poolDebt = poolState_.debt; result_.poolCollateral = poolState_.collateral; result_.remainingCollateral = borrower.collateral; if (vars.pledge) { // add new amount of collateral to pledge to borrower balance borrower.collateral += collateralToPledge_; result_.remainingCollateral += collateralToPledge_; result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // add new amount of collateral to pledge to pool balance result_.poolCollateral += collateralToPledge_; } if (vars.borrow) { // only intended recipient can borrow quote if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); vars.t0BorrowAmount = Maths.ceilWdiv(amountToBorrow_, poolState_.inflator); // t0 debt change is t0 amount to borrow plus the origination fee vars.t0DebtChange = Maths.wmul(vars.t0BorrowAmount, _borrowFeeRate(poolState_.rate) + Maths.WAD); borrower.t0Debt += vars.t0DebtChange; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); // check that drawing debt doesn't leave borrower debt under pool min debt amount _revertOnMinDebt( loans_, result_.poolDebt, vars.borrowerDebt, poolState_.quoteTokenScale ); // add debt change to pool's debt result_.t0PoolDebt += vars.t0DebtChange; result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // revert if borrow drives LUP price under the specified price limit _revertIfPriceDroppedBelowLimit(result_.newLup, limitIndex_); // use new lup to check borrow action won't push borrower into a state of under-collateralization // this check also covers the scenario when loan is already auctioned if (!_isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType)) { revert BorrowerUnderCollateralized(); } // stamp borrower Np to Tp ratio when draw debt vars.stampNpTpRatio = true; } // update loan state Loans.update( loans_, borrower, borrowerAddress_, poolState_.rate, false, // loan not in auction vars.stampNpTpRatio ); result_.debtPostAction = borrower.t0Debt; result_.collateralPostAction = borrower.collateral; } /** * @notice See `IERC20PoolBorrowerActions` and `IERC721PoolBorrowerActions` for descriptions * @dev === Write state === * @dev - `Loans.update` (`_upsert`): * @dev insert or update loan in loans array * @dev remove loan from loans array * @dev update borrower in `address => borrower` mapping * @dev === Reverts on === * @dev no debt to repay `NoDebt()` * @dev borrower debt less than pool min debt `AmountLTMinDebt()` * @dev borrower not sender `BorrowerNotSender()` * @dev not enough collateral to pull `InsufficientCollateral()` * @dev limit price reached `LimitIndexExceeded()` * @dev borrower cannot be in auction `AuctionActive()` */ function repayDebt( AuctionsState storage auctions_, DepositsState storage deposits_, LoansState storage loans_, PoolState calldata poolState_, address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, uint256 collateralAmountToPull_, uint256 limitIndex_ ) external returns ( RepayDebtResult memory result_ ) { RepayDebtLocalVars memory vars; vars.repay = maxQuoteTokenAmountToRepay_ != 0; vars.pull = collateralAmountToPull_ != 0; // revert if no amount to pull or repay if (!vars.repay && !vars.pull) revert InvalidAmount(); if (_inAuction(auctions_, borrowerAddress_)) revert AuctionActive(); Borrower memory borrower = loans_.borrowers[borrowerAddress_]; vars.borrowerDebt = Maths.wmul(borrower.t0Debt, poolState_.inflator); result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; result_.t0PoolDebt = poolState_.t0Debt; result_.poolDebt = poolState_.debt; result_.poolCollateral = poolState_.collateral; result_.remainingCollateral = borrower.collateral; if (vars.repay) { if (borrower.t0Debt == 0) revert NoDebt(); if (maxQuoteTokenAmountToRepay_ == type(uint256).max) { vars.t0RepaidDebt = borrower.t0Debt; } else { vars.t0RepaidDebt = Maths.min( borrower.t0Debt, Maths.floorWdiv(maxQuoteTokenAmountToRepay_, poolState_.inflator) ); } result_.quoteTokenToRepay = Maths.ceilWmul(vars.t0RepaidDebt, poolState_.inflator); // revert if (due to roundings) calculated token amount to repay is 0 if (result_.quoteTokenToRepay == 0) revert InvalidAmount(); result_.t0PoolDebt -= vars.t0RepaidDebt; result_.poolDebt = Maths.wmul(result_.t0PoolDebt, poolState_.inflator); vars.borrowerDebt = Maths.wmul(borrower.t0Debt - vars.t0RepaidDebt, poolState_.inflator); // check that paying the loan doesn't leave borrower debt under min debt amount _revertOnMinDebt( loans_, result_.poolDebt, vars.borrowerDebt, poolState_.quoteTokenScale ); result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); borrower.t0Debt -= vars.t0RepaidDebt; } if (vars.pull) { // only intended recipient can pull collateral if (borrowerAddress_ != msg.sender) revert BorrowerNotSender(); // calculate LUP only if it wasn't calculated in repay action if (!vars.repay) result_.newLup = Deposits.getLup(deposits_, result_.poolDebt); // prevent underflow if (collateralAmountToPull_ > borrower.collateral) revert InsufficientCollateral(); // check collateralization borrower.collateral -= collateralAmountToPull_; if (!_isCollateralized(vars.borrowerDebt, borrower.collateral, result_.newLup, poolState_.poolType)) revert InsufficientCollateral(); // stamp borrower Np to Tp ratio when pull collateral action vars.stampNpTpRatio = true; result_.poolCollateral -= collateralAmountToPull_; } // check limit price and revert if price dropped below _revertIfPriceDroppedBelowLimit(result_.newLup, limitIndex_); // update loan state Loans.update( loans_, borrower, borrowerAddress_, poolState_.rate, false, // loan not in auction vars.stampNpTpRatio ); result_.debtPostAction = borrower.t0Debt; result_.collateralPostAction = borrower.collateral; } /** * @notice See `IPoolBorrowerActions` for descriptions * @dev === Write state === * @dev - `Loans.update` (`_upsert`): * @dev insert or update loan in loans array * @dev remove loan from loans array * @dev update borrower in `address => borrower` mapping * @dev === Reverts on === * @dev auction active `AuctionActive()` * @dev loan not fully collateralized `BorrowerUnderCollateralized()` * @dev === Emit events === * @dev - `LoanStamped` */ function stampLoan( AuctionsState storage auctions_, DepositsState storage deposits_, LoansState storage loans_, PoolState calldata poolState_ ) external returns ( uint256 newLup_ ) { // revert if loan is in auction if (_inAuction(auctions_, msg.sender)) revert AuctionActive(); Borrower memory borrower = loans_.borrowers[msg.sender]; newLup_ = Deposits.getLup(deposits_, poolState_.debt); // revert if loan is not fully collateralized at current LUP if ( !_isCollateralized( Maths.wmul(borrower.t0Debt, poolState_.inflator), // current borrower debt borrower.collateral, newLup_, poolState_.poolType ) ) revert BorrowerUnderCollateralized(); // update loan state to stamp Np to Tp ratio Loans.update( loans_, borrower, msg.sender, poolState_.rate, false, // loan not in auction true // stamp Np to Tp ratio of the loan ); emit LoanStamped(msg.sender); } /**********************/ /*** View Functions ***/ /**********************/ /** * @notice Returns `true` if borrower is in auction. * @dev Used to accuratley increment and decrement `t0DebtInAuction` accumulator. * @param auctions_ Struct for pool auctions state. * @param borrower_ Borrower address to check auction status for. * @return `True` if borrower is in auction. */ function _inAuction( AuctionsState storage auctions_, address borrower_ ) internal view returns (bool) { return auctions_.liquidations[borrower_].kickTime != 0; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { AddQuoteParams, MoveQuoteParams, RemoveQuoteParams } from '../../interfaces/pool/commons/IPoolInternals.sol'; import { Bucket, DepositsState, Lender, PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; import { _depositFeeRate, _htp, _priceAt, MAX_FENWICK_INDEX } from '../helpers/PoolHelper.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Maths } from '../internal/Maths.sol'; /** @title LenderActions library @notice External library containing logic for lender actors: - `Lenders`: add, remove and move quote tokens; - `Traders`: add, remove and move quote tokens; add and remove collateral */ library LenderActions { /*************************/ /*** Local Var Structs ***/ /*************************/ /// @dev Struct used for `moveQuoteToken` function local vars. struct MoveQuoteLocalVars { uint256 fromBucketPrice; // [WAD] Price of the bucket to move amount from. uint256 fromBucketCollateral; // [WAD] Total amount of collateral in from bucket. uint256 fromBucketLP; // [WAD] Total amount of LP in from bucket. uint256 fromBucketLenderLP; // [WAD] Amount of LP owned by lender in from bucket. uint256 fromBucketDepositTime; // Time of lender deposit in the bucket to move amount from. uint256 fromBucketRemainingLP; // Amount of LP remaining in from bucket after move. uint256 fromBucketRemainingDeposit; // Amount of scaled deposit remaining in from bucket after move. uint256 toBucketPrice; // [WAD] Price of the bucket to move amount to. uint256 toBucketBankruptcyTime; // Time the bucket to move amount to was marked as insolvent. uint256 toBucketDepositTime; // Time of lender deposit in the bucket to move amount to. uint256 toBucketUnscaledDeposit; // Amount of unscaled deposit in to bucket. uint256 toBucketDeposit; // Amount of scaled deposit in to bucket. uint256 toBucketScale; // Scale deposit of to bucket. uint256 htp; // [WAD] Highest Threshold Price. } /// @dev Struct used for `removeQuoteToken` function local vars. struct RemoveDepositParams { uint256 depositConstraint; // [WAD] Constraint on deposit in quote token. uint256 lpConstraint; // [WAD] Constraint in LPB terms. uint256 bucketLP; // [WAD] Total LPB in the bucket. uint256 bucketCollateral; // [WAD] Claimable collateral in the bucket. uint256 price; // [WAD] Price of bucket. uint256 index; // Bucket index. uint256 dustLimit; // Minimum amount of deposit which may reside in a bucket. } /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event AddQuoteToken(address indexed lender, uint256 indexed index, uint256 amount, uint256 lpAwarded, uint256 lup); event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); event MoveQuoteToken(address indexed lender, uint256 indexed from, uint256 indexed to, uint256 amount, uint256 lpRedeemedFrom, uint256 lpAwardedTo, uint256 lup); event RemoveQuoteToken(address indexed lender, uint256 indexed index, uint256 amount, uint256 lpRedeemed, uint256 lup); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error BucketBankruptcyBlock(); error CannotMergeToHigherPrice(); error DustAmountNotExceeded(); error InvalidIndex(); error InvalidAmount(); error LUPBelowHTP(); error NoClaim(); error InsufficientLP(); error InsufficientLiquidity(); error InsufficientCollateral(); error MoveToSameIndex(); /***************************/ /*** External Functions ***/ /***************************/ /** * @notice See `IERC20PoolLenderActions` and `IERC721PoolLenderActions` for descriptions * @dev === Write state === * @dev - `Buckets.addCollateral`: * @dev increment `bucket.collateral` and `bucket.lps` accumulator * @dev `addLenderLP`: increment `lender.lps` accumulator and `lender.depositTime `state * @dev === Reverts on === * @dev invalid bucket index `InvalidIndex()` * @dev no LP awarded in bucket `InsufficientLP()` */ function addCollateral( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, uint256 collateralAmountToAdd_, uint256 index_ ) external returns (uint256 bucketLP_) { // revert if no amount to be added if (collateralAmountToAdd_ == 0) revert InvalidAmount(); // revert if adding at invalid index if (index_ == 0 || index_ > MAX_FENWICK_INDEX) revert InvalidIndex(); uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); uint256 bucketPrice = _priceAt(index_); bucketLP_ = Buckets.addCollateral( buckets_[index_], msg.sender, bucketDeposit, collateralAmountToAdd_, bucketPrice ); // revert if (due to rounding) the awarded LP is 0 if (bucketLP_ == 0) revert InsufficientLP(); } /** * @notice See `IPoolLenderActions` for descriptions * @dev === Write state === * @dev - `Deposits.unscaledAdd` (add new amount in `Fenwick` tree): update `values` array state * @dev - increment `bucket.lps` accumulator * @dev - increment `lender.lps` accumulator and `lender.depositTime` state * @dev === Reverts on === * @dev invalid bucket index `InvalidIndex()` * @dev same block when bucket becomes insolvent `BucketBankruptcyBlock()` * @dev no LP awarded in bucket `InsufficientLP()` * @dev calculated unscaled amount to add is 0 `InvalidAmount()` * @dev === Emit events === * @dev - `AddQuoteToken` */ function addQuoteToken( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, PoolState calldata poolState_, AddQuoteParams calldata params_ ) external returns (uint256 bucketLP_, uint256 addedAmount_, uint256 lup_) { // revert if no amount to be added if (params_.amount == 0) revert InvalidAmount(); // revert if adding to an invalid index if (params_.index == 0 || params_.index > MAX_FENWICK_INDEX) revert InvalidIndex(); Bucket storage bucket = buckets_[params_.index]; uint256 bankruptcyTime = bucket.bankruptcyTime; // cannot deposit in the same block when bucket becomes insolvent if (bankruptcyTime == block.timestamp) revert BucketBankruptcyBlock(); uint256 unscaledBucketDeposit = Deposits.unscaledValueAt(deposits_, params_.index); uint256 bucketScale = Deposits.scale(deposits_, params_.index); uint256 bucketDeposit = Maths.wmul(bucketScale, unscaledBucketDeposit); uint256 bucketPrice = _priceAt(params_.index); // calculate added amount charging deposit fee addedAmount_ = Maths.wmul(params_.amount, Maths.WAD - _depositFeeRate(poolState_.rate)); bucketLP_ = Buckets.quoteTokensToLP( bucket.collateral, bucket.lps, bucketDeposit, addedAmount_, bucketPrice, Math.Rounding.Down ); // revert if (due to rounding) the awarded LP is 0 if (bucketLP_ == 0) revert InsufficientLP(); Deposits.unscaledAdd(deposits_, params_.index, Maths.wdiv(addedAmount_, bucketScale)); // update lender LP Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, bucketLP_); // update bucket LP bucket.lps += bucketLP_; // calculate new LUP uint256 lupIndex = Deposits.findIndexOfSum(deposits_, poolState_.debt); lup_ = _priceAt(lupIndex); emit AddQuoteToken( msg.sender, params_.index, addedAmount_, bucketLP_, lup_ ); } /** * @notice See `IPoolLenderActions` for descriptions * @dev === Write state === * @dev - `_removeMaxDeposit`: * @dev `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): update `values` array state * @dev - `Deposits.unscaledAdd` (add amount in `Fenwick` tree, to index): update `values` array state * @dev - decrement `lender.lps` accumulator for from bucket * @dev - increment `lender.lps` accumulator and `lender.depositTime` state for to bucket * @dev - decrement `bucket.lps` accumulator for from bucket * @dev - increment `bucket.lps` accumulator for to bucket * @dev === Reverts on === * @dev same index `MoveToSameIndex()` * @dev dust amount `DustAmountNotExceeded()` * @dev invalid index `InvalidIndex()` * @dev no LP awarded in to bucket `InsufficientLP()` * @dev calculated unscaled amount to move is 0 `InvalidAmount()` * @dev === Emit events === * @dev - `BucketBankruptcy` * @dev - `MoveQuoteToken` */ function moveQuoteToken( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, PoolState calldata poolState_, MoveQuoteParams calldata params_ ) external returns (uint256 fromBucketRedeemedLP_, uint256 toBucketLP_, uint256 movedAmount_, uint256 lup_) { if (params_.fromIndex == params_.toIndex) revert MoveToSameIndex(); if (params_.maxAmountToMove < poolState_.quoteTokenScale) revert DustAmountNotExceeded(); if (params_.toIndex == 0 || params_.toIndex > MAX_FENWICK_INDEX) revert InvalidIndex(); Bucket storage toBucket = buckets_[params_.toIndex]; MoveQuoteLocalVars memory vars; vars.toBucketBankruptcyTime = toBucket.bankruptcyTime; // cannot move in the same block when target bucket becomes insolvent if (vars.toBucketBankruptcyTime == block.timestamp) revert BucketBankruptcyBlock(); Bucket storage fromBucket = buckets_[params_.fromIndex]; Lender storage fromBucketLender = fromBucket.lenders[msg.sender]; vars.fromBucketPrice = _priceAt(params_.fromIndex); vars.fromBucketCollateral = fromBucket.collateral; vars.fromBucketLP = fromBucket.lps; vars.fromBucketDepositTime = fromBucketLender.depositTime; vars.toBucketPrice = _priceAt(params_.toIndex); if (fromBucket.bankruptcyTime < vars.fromBucketDepositTime) vars.fromBucketLenderLP = fromBucketLender.lps; (movedAmount_, fromBucketRedeemedLP_, vars.fromBucketRemainingDeposit) = _removeMaxDeposit( deposits_, RemoveDepositParams({ depositConstraint: params_.maxAmountToMove, lpConstraint: vars.fromBucketLenderLP, bucketLP: vars.fromBucketLP, bucketCollateral: vars.fromBucketCollateral, price: vars.fromBucketPrice, index: params_.fromIndex, dustLimit: poolState_.quoteTokenScale }) ); // apply deposit fee if moving to a lower-priced bucket if (params_.fromIndex < params_.toIndex) { movedAmount_ = Maths.wmul(movedAmount_, Maths.WAD - _depositFeeRate(poolState_.rate)); } vars.toBucketUnscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.toIndex); vars.toBucketScale = Deposits.scale(deposits_, params_.toIndex); vars.toBucketDeposit = Maths.wmul(vars.toBucketUnscaledDeposit, vars.toBucketScale); toBucketLP_ = Buckets.quoteTokensToLP( toBucket.collateral, toBucket.lps, vars.toBucketDeposit, movedAmount_, vars.toBucketPrice, Math.Rounding.Down ); // revert if (due to rounding) the awarded LP in to bucket is 0 if (toBucketLP_ == 0) revert InsufficientLP(); Deposits.unscaledAdd(deposits_, params_.toIndex, Maths.wdiv(movedAmount_, vars.toBucketScale)); // recalculate LUP and HTP lup_ = Deposits.getLup(deposits_, poolState_.debt); vars.htp = _htp(params_.maxT0DebtToCollateral, poolState_.inflator); // check loan book's htp against new lup, revert if move drives LUP below HTP if ( params_.fromIndex < params_.toIndex && ( // check loan book's htp doesn't exceed new lup vars.htp > lup_ || // ensure that pool debt < deposits after move // this can happen if deposit fee is applied when moving amount (poolState_.debt != 0 && poolState_.debt > Deposits.treeSum(deposits_)) ) ) revert LUPBelowHTP(); // update lender and bucket LP balance in from bucket vars.fromBucketRemainingLP = vars.fromBucketLP - fromBucketRedeemedLP_; // check if from bucket healthy after move quote tokens - set bankruptcy if collateral and deposit are 0 but there's still LP if (vars.fromBucketCollateral == 0 && vars.fromBucketRemainingDeposit == 0 && vars.fromBucketRemainingLP != 0) { fromBucket.lps = 0; fromBucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( params_.fromIndex, vars.fromBucketRemainingLP ); } else { // update lender and bucket LP balance fromBucketLender.lps -= fromBucketRedeemedLP_; fromBucket.lps = vars.fromBucketRemainingLP; } // update lender and bucket LP balance in target bucket Lender storage toBucketLender = toBucket.lenders[msg.sender]; vars.toBucketDepositTime = toBucketLender.depositTime; if (vars.toBucketBankruptcyTime >= vars.toBucketDepositTime) { // bucket is bankrupt and deposit was done before bankruptcy time, reset lender lp amount toBucketLender.lps = toBucketLP_; // set deposit time of the lender's to bucket as bucket's last bankruptcy timestamp + 1 so deposit won't get invalidated vars.toBucketDepositTime = vars.toBucketBankruptcyTime + 1; } else { toBucketLender.lps += toBucketLP_; } // set deposit time to the greater of the lender's from bucket and the target bucket toBucketLender.depositTime = Maths.max(vars.fromBucketDepositTime, vars.toBucketDepositTime); // update bucket LP balance toBucket.lps += toBucketLP_; emit MoveQuoteToken( msg.sender, params_.fromIndex, params_.toIndex, movedAmount_, fromBucketRedeemedLP_, toBucketLP_, lup_ ); } /** * @notice See `IPoolLenderActions` for descriptions * @dev === Write state === * @dev - `_removeMaxDeposit`: * @dev `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): update `values` array state * @dev - decrement `lender.lps` accumulator * @dev - decrement `bucket.lps` accumulator * @dev === Reverts on === * @dev no `LP` `NoClaim()`; * @dev `LUP` lower than `HTP` `LUPBelowHTP()` * @dev === Emit events === * @dev - `RemoveQuoteToken` * @dev - `BucketBankruptcy` */ function removeQuoteToken( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, PoolState calldata poolState_, RemoveQuoteParams calldata params_ ) external returns (uint256 removedAmount_, uint256 redeemedLP_, uint256 lup_) { // revert if no amount to be removed if (params_.maxAmount == 0) revert InvalidAmount(); Bucket storage bucket = buckets_[params_.index]; Lender storage lender = bucket.lenders[msg.sender]; uint256 depositTime = lender.depositTime; RemoveDepositParams memory removeParams; if (bucket.bankruptcyTime < depositTime) removeParams.lpConstraint = lender.lps; // revert if no LP to claim if (removeParams.lpConstraint == 0) revert NoClaim(); removeParams.depositConstraint = params_.maxAmount; removeParams.price = _priceAt(params_.index); removeParams.bucketLP = bucket.lps; removeParams.bucketCollateral = bucket.collateral; removeParams.index = params_.index; removeParams.dustLimit = poolState_.quoteTokenScale; uint256 unscaledRemaining; (removedAmount_, redeemedLP_, unscaledRemaining) = _removeMaxDeposit( deposits_, removeParams ); lup_ = Deposits.getLup(deposits_, poolState_.debt); uint256 htp = _htp(params_.maxT0DebtToCollateral, poolState_.inflator); if ( // check loan book's htp doesn't exceed new lup htp > lup_ || // ensure that pool debt < deposits after removal // this can happen if lup and htp are less than min bucket price and htp > lup (since LUP is capped at min bucket price) (poolState_.debt != 0 && poolState_.debt > Deposits.treeSum(deposits_)) ) revert LUPBelowHTP(); uint256 lpRemaining = removeParams.bucketLP - redeemedLP_; // check if bucket healthy after remove quote tokens - set bankruptcy if collateral and deposit are 0 but there's still LP if (removeParams.bucketCollateral == 0 && unscaledRemaining == 0 && lpRemaining != 0) { bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( params_.index, lpRemaining ); } else { // update lender and bucket LP balances lender.lps -= redeemedLP_; bucket.lps = lpRemaining; } emit RemoveQuoteToken( msg.sender, params_.index, removedAmount_, redeemedLP_, lup_ ); } /** * @notice See `IPoolLenderActions` for descriptions * @dev === Write state === * @dev decrement `lender.lps` accumulator * @dev decrement `bucket.collateral` and `bucket.lps` accumulator * @dev === Reverts on === * @dev not enough collateral `InsufficientCollateral()` * @dev no `LP` redeemed `InsufficientLP()` * @dev === Emit events === * @dev - `BucketBankruptcy` */ function removeCollateral( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, uint256 amount_, uint256 index_ ) external returns (uint256 lpAmount_) { // revert if no amount to be removed if (amount_ == 0) revert InvalidAmount(); Bucket storage bucket = buckets_[index_]; uint256 bucketCollateral = bucket.collateral; if (amount_ > bucketCollateral) revert InsufficientCollateral(); uint256 bucketPrice = _priceAt(index_); uint256 bucketLP = bucket.lps; uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); lpAmount_ = Buckets.collateralToLP( bucketCollateral, bucketLP, bucketDeposit, amount_, bucketPrice, Math.Rounding.Up ); // revert if (due to rounding) required LP is 0 if (lpAmount_ == 0) revert InsufficientLP(); Lender storage lender = bucket.lenders[msg.sender]; uint256 lenderLpBalance; if (bucket.bankruptcyTime < lender.depositTime) lenderLpBalance = lender.lps; if (lenderLpBalance == 0 || lpAmount_ > lenderLpBalance) revert InsufficientLP(); // update bucket LP and collateral balance bucketLP -= lpAmount_; // If clearing out the bucket collateral, ensure it's zeroed out if (bucketLP == 0 && bucketDeposit == 0) { amount_ = bucketCollateral; } bucketCollateral -= amount_; bucket.collateral = bucketCollateral; // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLP != 0) { bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( index_, bucketLP ); } else { // update lender and bucket LP balances lender.lps -= lpAmount_; bucket.lps = bucketLP; } } /** * @notice Removes max collateral amount from a given bucket index. * @dev === Write state === * @dev - `_removeMaxCollateral`: * @dev decrement `lender.lps` accumulator * @dev decrement `bucket.collateral` and `bucket.lps` accumulator * @dev === Reverts on === * @dev not enough collateral `InsufficientCollateral()` * @dev no claim `NoClaim()` * @dev leaves less than dust limit in bucket `DustAmountNotExceeded()` * @return Amount of collateral that was removed. * @return Amount of LP redeemed for removed collateral amount. */ function removeMaxCollateral( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, uint256 dustLimit_, uint256 maxAmount_, uint256 index_ ) external returns (uint256, uint256) { // revert if no amount to remove if (maxAmount_ == 0) revert InvalidAmount(); return _removeMaxCollateral( buckets_, deposits_, dustLimit_, maxAmount_, index_ ); } /** * @notice See `IERC721PoolLenderActions` for descriptions * @dev === Write state === * @dev - `Buckets.addCollateral`: * @dev increment `bucket.collateral` and `bucket.lps` accumulator * @dev increment `lender.lps` accumulator and `lender.depositTime` state * @dev === Reverts on === * @dev invalid merge index `CannotMergeToHigherPrice()` * @dev no `LP` awarded in `toIndex_` bucket `InsufficientLP()` * @dev no collateral removed from bucket `InvalidAmount()` */ function mergeOrRemoveCollateral( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, uint256[] calldata removalIndexes_, uint256 collateralAmount_, uint256 toIndex_ ) external returns (uint256 collateralToMerge_, uint256 bucketLP_) { uint256 i; uint256 fromIndex; uint256 collateralRemoved; uint256 noOfBuckets = removalIndexes_.length; uint256 collateralRemaining = collateralAmount_; // Loop over buckets, exit if collateralAmount is reached or max noOfBuckets is reached while (collateralToMerge_ < collateralAmount_ && i < noOfBuckets) { fromIndex = removalIndexes_[i]; if (fromIndex > toIndex_) revert CannotMergeToHigherPrice(); (collateralRemoved, ) = _removeMaxCollateral( buckets_, deposits_, 1, // dust limit is same as collateral scale collateralRemaining, fromIndex ); // revert if calculated amount of collateral to remove is 0 if (collateralRemoved == 0) revert InvalidAmount(); collateralToMerge_ += collateralRemoved; collateralRemaining = collateralRemaining - collateralRemoved; unchecked { ++i; } } if (collateralToMerge_ != collateralAmount_) { // Merge totalled collateral to specified bucket, toIndex_ uint256 toBucketDeposit = Deposits.valueAt(deposits_, toIndex_); uint256 toBucketPrice = _priceAt(toIndex_); bucketLP_ = Buckets.addCollateral( buckets_[toIndex_], msg.sender, toBucketDeposit, collateralToMerge_, toBucketPrice ); // revert if (due to rounding) the awarded LP is 0 if (bucketLP_ == 0) revert InsufficientLP(); } } /**************************/ /*** Internal Functions ***/ /**************************/ /** * @notice Removes max collateral amount from a given bucket index. * @dev === Write state === * @dev decrement `lender.lps` accumulator * @dev decrement `bucket.collateral` and `bucket.lps` accumulator * @dev === Reverts on === * @dev not enough collateral `InsufficientCollateral()` * @dev no claim `NoClaim()` * @dev no `LP` redeemed `InsufficientLP()` * @dev leaves less than dust limit in bucket `DustAmountNotExceeded()` * @dev === Emit events === * @dev - `BucketBankruptcy` * @return collateralAmount_ Amount of collateral that was removed. * @return lpAmount_ Amount of `LP` redeemed for removed collateral amount. */ function _removeMaxCollateral( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, uint256 dustLimit_, uint256 maxAmount_, uint256 index_ ) internal returns (uint256 collateralAmount_, uint256 lpAmount_) { Bucket storage bucket = buckets_[index_]; uint256 bucketCollateral = bucket.collateral; // revert if there's no collateral in bucket if (bucketCollateral == 0) revert InsufficientCollateral(); Lender storage lender = bucket.lenders[msg.sender]; uint256 lenderLpBalance; if (bucket.bankruptcyTime < lender.depositTime) lenderLpBalance = lender.lps; // revert if no LP to redeem if (lenderLpBalance == 0) revert NoClaim(); uint256 bucketPrice = _priceAt(index_); uint256 bucketLP = bucket.lps; uint256 bucketDeposit = Deposits.valueAt(deposits_, index_); // limit amount by what is available in the bucket collateralAmount_ = Maths.min(maxAmount_, bucketCollateral); // determine how much LP would be required to remove the requested amount uint256 requiredLP = Buckets.collateralToLP( bucketCollateral, bucketLP, bucketDeposit, collateralAmount_, bucketPrice, Math.Rounding.Up ); // revert if (due to rounding) the required LP is 0 if (requiredLP == 0) revert InsufficientLP(); // limit withdrawal by the lender's LPB if (requiredLP <= lenderLpBalance) { // withdraw collateralAmount_ as is lpAmount_ = requiredLP; } else { lpAmount_ = lenderLpBalance; collateralAmount_ = Math.mulDiv(lenderLpBalance, collateralAmount_, requiredLP); if (collateralAmount_ == 0) revert InsufficientLP(); } // update bucket LP and collateral balance bucketLP -= Maths.min(bucketLP, lpAmount_); // If clearing out the bucket collateral, ensure it's zeroed out if (bucketLP == 0 && bucketDeposit == 0) collateralAmount_ = bucketCollateral; collateralAmount_ = Maths.min(bucketCollateral, collateralAmount_); bucketCollateral -= collateralAmount_; if (bucketCollateral != 0 && bucketCollateral < dustLimit_) revert DustAmountNotExceeded(); bucket.collateral = bucketCollateral; // check if bucket healthy after collateral remove - set bankruptcy if collateral and deposit are 0 but there's still LP if (bucketCollateral == 0 && bucketDeposit == 0 && bucketLP != 0) { bucket.lps = 0; bucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( index_, bucketLP ); } else { // update lender and bucket LP balances lender.lps -= lpAmount_; bucket.lps = bucketLP; } } /** * @notice Removes the amount of quote tokens calculated for the given amount of LP. * @dev === Write state === * @dev - `Deposits.unscaledRemove` (remove amount in `Fenwick` tree, from index): * @dev update `values` array state * @dev === Reverts on === * @dev no `LP` redeemed `InsufficientLP()` * @dev no unscaled amount removed `InvalidAmount()` * @return removedAmount_ Amount of scaled deposit removed. * @return redeemedLP_ Amount of bucket `LP` corresponding for calculated scaled deposit amount. * @return unscaledRemaining_ Amount of unscaled deposit remaining. */ function _removeMaxDeposit( DepositsState storage deposits_, RemoveDepositParams memory params_ ) internal returns (uint256 removedAmount_, uint256 redeemedLP_, uint256 unscaledRemaining_) { uint256 unscaledDepositAvailable = Deposits.unscaledValueAt(deposits_, params_.index); // revert if there's no liquidity available to remove if (unscaledDepositAvailable == 0) revert InsufficientLiquidity(); uint256 depositScale = Deposits.scale(deposits_, params_.index); uint256 scaledDepositAvailable = Maths.wmul(unscaledDepositAvailable, depositScale); // Below is pseudocode explaining the logic behind finding the constrained amount of deposit and LPB // scaledRemovedAmount is constrained by the scaled maxAmount(in QT), the scaledDeposit constraint, and // the lender LPB exchange rate in scaled deposit-to-LPB for the bucket: // scaledRemovedAmount = min ( maxAmount_, scaledDeposit, lenderLPBalance*exchangeRate) // redeemedLP_ = min ( maxAmount_/scaledExchangeRate, scaledDeposit/exchangeRate, lenderLPBalance) uint256 scaledLpConstraint = Buckets.lpToQuoteTokens( params_.bucketCollateral, params_.bucketLP, scaledDepositAvailable, params_.lpConstraint, params_.price, Math.Rounding.Down ); uint256 unscaledRemovedAmount; if ( params_.depositConstraint < scaledDepositAvailable && params_.depositConstraint < scaledLpConstraint ) { // depositConstraint is binding constraint removedAmount_ = params_.depositConstraint; redeemedLP_ = Buckets.quoteTokensToLP( params_.bucketCollateral, params_.bucketLP, scaledDepositAvailable, removedAmount_, params_.price, Math.Rounding.Up ); redeemedLP_ = Maths.min(redeemedLP_, params_.lpConstraint); unscaledRemovedAmount = Maths.wdiv(removedAmount_, depositScale); } else if (scaledDepositAvailable < scaledLpConstraint) { // scaledDeposit is binding constraint removedAmount_ = scaledDepositAvailable; redeemedLP_ = Buckets.quoteTokensToLP( params_.bucketCollateral, params_.bucketLP, scaledDepositAvailable, removedAmount_, params_.price, Math.Rounding.Up ); redeemedLP_ = Maths.min(redeemedLP_, params_.lpConstraint); unscaledRemovedAmount = unscaledDepositAvailable; } else { // redeeming all LP redeemedLP_ = params_.lpConstraint; removedAmount_ = Buckets.lpToQuoteTokens( params_.bucketCollateral, params_.bucketLP, scaledDepositAvailable, redeemedLP_, params_.price, Math.Rounding.Down ); unscaledRemovedAmount = Maths.wdiv(removedAmount_, depositScale); } // If clearing out the bucket deposit, ensure it's zeroed out if (redeemedLP_ == params_.bucketLP) { removedAmount_ = scaledDepositAvailable; unscaledRemovedAmount = unscaledDepositAvailable; } scaledDepositAvailable -= removedAmount_; if (scaledDepositAvailable != 0 && scaledDepositAvailable < params_.dustLimit) revert DustAmountNotExceeded(); unscaledRemaining_ = unscaledDepositAvailable - unscaledRemovedAmount; // revert if (due to rounding) required LP is 0 if (redeemedLP_ == 0) revert InsufficientLP(); // update FenwickTree Deposits.unscaledRemove(deposits_, params_.index, unscaledRemovedAmount); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { PoolType } from '../../interfaces/pool/IPool.sol'; import { AuctionsState, Borrower, Bucket, DepositsState, Kicker, Liquidation, LoansState, PoolState, ReserveAuctionState } from '../../interfaces/pool/commons/IPoolState.sol'; import { SettleParams, SettleResult } from '../../interfaces/pool/commons/IPoolInternals.sol'; import { _auctionPrice, _indexOf, _priceAt, MAX_FENWICK_INDEX, MIN_PRICE, DEPOSIT_BUFFER } from '../helpers/PoolHelper.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Loans } from '../internal/Loans.sol'; import { Maths } from '../internal/Maths.sol'; /** @title Auction settler library @notice External library containing actions involving auctions within pool: - `settle` auctions */ library SettlerActions { /*************************/ /*** Local Var Structs ***/ /*************************/ /// @dev Struct used for `_settlePoolDebtWithDeposit` function local vars. struct SettleLocalVars { uint256 collateralUsed; // [WAD] collateral used to settle debt uint256 debt; // [WAD] debt to settle uint256 hpbCollateral; // [WAD] amount of collateral in HPB bucket uint256 hpbUnscaledDeposit; // [WAD] unscaled amount of of quote tokens in HPB bucket before settle uint256 hpbLP; // [WAD] amount of LP in HPB bucket uint256 index; // index of settling bucket uint256 maxSettleableDebt; // [WAD] max amount that can be settled with existing collateral uint256 price; // [WAD] price of settling bucket uint256 scaledDeposit; // [WAD] scaled amount of quote tokens in bucket uint256 scale; // [WAD] scale of settling bucket uint256 unscaledDeposit; // [WAD] unscaled amount of quote tokens in bucket } /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event AuctionSettle(address indexed borrower, uint256 collateral); event AuctionNFTSettle(address indexed borrower, uint256 collateral, uint256 lp, uint256 index); event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); event Settle(address indexed borrower, uint256 settledDebt); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error AuctionNotClearable(); error NoAuction(); /***************************/ /*** External Functions ***/ /***************************/ /** * @notice See `IPoolSettlerActions` for descriptions. * @notice Settles the debt of the given loan / borrower by performing following steps: * 1. settle debt with `HPB`s deposit, up to specified buckets depth. * 2. settle debt with pool reserves (if there's still debt and no collateral left after step 1). * 3. forgive bad debt from next `HPB`, up to remaining buckets depth (and if there's still debt after step 2). * @dev === Write state === * @dev update borrower state * @dev === Reverts on === * @dev loan is not in auction `NoAuction()` * @dev `72` hours didn't pass and auction still has collateral `AuctionNotClearable()` * @dev === Emit events === * @dev - `Settle` * @return result_ The `SettleResult` struct result of settle action. */ function settlePoolDebt( AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, LoansState storage loans_, ReserveAuctionState storage reserveAuction_, PoolState calldata poolState_, SettleParams memory params_ ) external returns (SettleResult memory result_) { uint256 kickTime = auctions_.liquidations[params_.borrower].kickTime; if (kickTime == 0) revert NoAuction(); Borrower memory borrower = loans_.borrowers[params_.borrower]; if ((block.timestamp - kickTime <= 72 hours) && (borrower.collateral != 0)) revert AuctionNotClearable(); result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; result_.t0DebtSettled = borrower.t0Debt; result_.collateralSettled = borrower.collateral; // 1. settle debt with HPB deposit ( borrower.t0Debt, borrower.collateral, params_.bucketDepth ) = _settlePoolDebtWithDeposit( buckets_, deposits_, params_, borrower, poolState_.inflator ); if (borrower.t0Debt != 0 && borrower.collateral == 0) { // 2. settle debt with pool reserves uint256 assets = Maths.floorWmul(poolState_.t0Debt - result_.t0DebtSettled + borrower.t0Debt, poolState_.inflator) + params_.poolBalance; uint256 deposits = Deposits.treeSum(deposits_); uint256 liabilities = // require 1.0 + 1e-9 deposit buffer (extra margin) for deposits Maths.wmul(DEPOSIT_BUFFER, deposits) + auctions_.totalBondEscrowed + reserveAuction_.unclaimed; // settle debt from reserves (assets - liabilities) if reserves positive, round reserves down however // capped at half of the origination fee rate, based on current book fees if (assets > liabilities) { uint256 t0ReserveSettleAmount = Maths.min(Maths.floorWdiv(assets - liabilities, poolState_.inflator), borrower.t0Debt); // if the settlement phase of 144 hours has not ended, settle up to the borrower reserve limit if (deposits > 0 && block.timestamp - kickTime < 144 hours) { // retrieve amount of debt that can be settled with reserves uint256 reserveSettleLimit = auctions_.liquidations[params_.borrower].t0ReserveSettleAmount; // calculate reserve amount to be used when settling the auction t0ReserveSettleAmount = Maths.min(t0ReserveSettleAmount, reserveSettleLimit); reserveSettleLimit -= t0ReserveSettleAmount; // store remaining amount limit to settle auctions_.liquidations[params_.borrower].t0ReserveSettleAmount = reserveSettleLimit; } borrower.t0Debt -= t0ReserveSettleAmount; } // 3. forgive bad debt from next HPB if (borrower.t0Debt != 0) { borrower.t0Debt = _forgiveBadDebt( buckets_, deposits_, params_, borrower, poolState_.inflator ); } } // complete result struct with debt settled result_.t0DebtSettled -= borrower.t0Debt; result_.debtSettled = Maths.wmul(result_.t0DebtSettled, poolState_.inflator); emit Settle(params_.borrower, result_.debtSettled); // if entire debt was settled then settle auction if (borrower.t0Debt == 0) { (borrower.collateral, ) = _settleAuction( auctions_, buckets_, deposits_, params_.borrower, borrower.collateral, poolState_.poolType ); } // complete result struct with debt and collateral post action and collateral settled result_.debtPostAction = borrower.t0Debt; result_.collateralRemaining = borrower.collateral; result_.collateralSettled -= result_.collateralRemaining; // update borrower state loans_.borrowers[params_.borrower] = borrower; } /***************************/ /*** Internal Functions ***/ /***************************/ /** * @notice Performs auction settle based on pool type, emits settle event and removes auction from auctions queue. * @dev === Emit events === * @dev - `AuctionNFTSettle` or `AuctionSettle` * @param auctions_ Struct for pool auctions state. * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param borrowerAddress_ Address of the borrower that exits auction. * @param borrowerCollateral_ Borrower collateral amount before auction exit (in `NFT` could be fragmented as result of partial takes). * @param poolType_ Type of the pool (can be `ERC20` or `ERC721`). * @return remainingCollateral_ Collateral remaining after auction is settled (same amount for `ERC20` pool, rounded collateral for `ERC721` pool). * @return compensatedCollateral_ Amount of collateral compensated (`ERC721` settle only), to be deducted from pool pledged collateral accumulator. Always `0` for `ERC20` pools. */ function _settleAuction( AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, address borrowerAddress_, uint256 borrowerCollateral_, uint256 poolType_ ) internal returns (uint256 remainingCollateral_, uint256 compensatedCollateral_) { if (poolType_ == uint8(PoolType.ERC721)) { uint256 lp; uint256 bucketIndex; // floor collateral of borrower remainingCollateral_ = (borrowerCollateral_ / Maths.WAD) * Maths.WAD; // if there's fraction of NFTs remaining then reward difference to borrower as LP in auction price bucket if (remainingCollateral_ != borrowerCollateral_) { // calculate the amount of collateral that should be compensated with LP compensatedCollateral_ = borrowerCollateral_ - remainingCollateral_; uint256 auctionPrice = _auctionPrice( auctions_.liquidations[borrowerAddress_].referencePrice, auctions_.liquidations[borrowerAddress_].kickTime ); // determine the bucket index to compensate fractional collateral bucketIndex = auctionPrice > MIN_PRICE ? _indexOf(auctionPrice) : MAX_FENWICK_INDEX; // deposit collateral in bucket and reward LP to compensate fractional collateral lp = Buckets.addCollateral( buckets_[bucketIndex], borrowerAddress_, Deposits.valueAt(deposits_, bucketIndex), compensatedCollateral_, _priceAt(bucketIndex) ); } emit AuctionNFTSettle( borrowerAddress_, remainingCollateral_, lp, bucketIndex ); } else { remainingCollateral_ = borrowerCollateral_; emit AuctionSettle( borrowerAddress_, remainingCollateral_ ); } _removeAuction(auctions_, borrowerAddress_); } /** * @notice Removes auction and repairs the queue order. * @notice Updates kicker's claimable balance with bond size awarded and subtracts bond size awarded from `liquidationBondEscrowed`. * @dev === Write state === * @dev decrement kicker locked accumulator, increment kicker claimable accumumlator * @dev decrement auctions count accumulator * @dev update auction queue state * @param auctions_ Struct for pool auctions state. * @param borrower_ Auctioned borrower address. */ function _removeAuction( AuctionsState storage auctions_, address borrower_ ) internal { Liquidation memory liquidation = auctions_.liquidations[borrower_]; // update kicker balances Kicker storage kicker = auctions_.kickers[liquidation.kicker]; kicker.locked -= liquidation.bondSize; kicker.claimable += liquidation.bondSize; // decrement number of active auctions -- auctions_.noOfAuctions; // update auctions queue if (auctions_.head == borrower_ && auctions_.tail == borrower_) { // liquidation is the head and tail auctions_.head = address(0); auctions_.tail = address(0); } else if (auctions_.head == borrower_) { // liquidation is the head auctions_.liquidations[liquidation.next].prev = address(0); auctions_.head = liquidation.next; } else if (auctions_.tail == borrower_) { // liquidation is the tail auctions_.liquidations[liquidation.prev].next = address(0); auctions_.tail = liquidation.prev; } else { // liquidation is in the middle auctions_.liquidations[liquidation.prev].next = liquidation.next; auctions_.liquidations[liquidation.next].prev = liquidation.prev; } // delete liquidation delete auctions_.liquidations[borrower_]; } /** * @notice Called to settle debt using `HPB` deposits, up to the number of specified buckets depth. * @dev === Write state === * @dev - `Deposits.unscaledRemove()` (remove amount in `Fenwick` tree, from index): * @dev update `values` array state * @dev - `Buckets.addCollateral`: * @dev increment `bucket.collateral` and `bucket.lps` accumulator * @dev increment `lender.lps` accumulator and `lender.depositTime` state * @dev === Emit events === * @dev - `BucketBankruptcy` * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param params_ Struct containing params for settle action. * @param borrower_ Struct containing borrower details. * @param inflator_ Current pool inflator. * @return remainingt0Debt_ Remaining borrower `t0` debt after settle with `HPB`. * @return remainingCollateral_ Remaining borrower collateral after settle with `HPB`. * @return bucketDepth_ Number of buckets to use for forgiving debt in case there's more remaining. */ function _settlePoolDebtWithDeposit( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, SettleParams memory params_, Borrower memory borrower_, uint256 inflator_ ) internal returns (uint256 remainingt0Debt_, uint256 remainingCollateral_, uint256 bucketDepth_) { remainingt0Debt_ = borrower_.t0Debt; remainingCollateral_ = borrower_.collateral; bucketDepth_ = params_.bucketDepth; while (bucketDepth_ != 0 && remainingt0Debt_ != 0 && remainingCollateral_ != 0) { SettleLocalVars memory vars; (vars.index, , vars.scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); vars.hpbUnscaledDeposit = Deposits.unscaledValueAt(deposits_, vars.index); vars.unscaledDeposit = vars.hpbUnscaledDeposit; vars.price = _priceAt(vars.index); if (vars.unscaledDeposit != 0) { vars.debt = Maths.wmul(remainingt0Debt_, inflator_); // current debt to be settled vars.maxSettleableDebt = Maths.floorWmul(remainingCollateral_, vars.price); // max debt that can be settled with existing collateral vars.scaledDeposit = Maths.wmul(vars.scale, vars.unscaledDeposit); // 1) bucket deposit covers remaining loan debt to settle, loan's collateral can cover remaining loan debt to settle if (vars.scaledDeposit >= vars.debt && vars.maxSettleableDebt >= vars.debt) { // remove only what's needed to settle the debt vars.unscaledDeposit = Maths.wdiv(vars.debt, vars.scale); vars.collateralUsed = Maths.ceilWdiv(vars.debt, vars.price); // settle the entire debt remainingt0Debt_ = 0; } // 2) bucket deposit can not cover all of loan's remaining debt, bucket deposit is the constraint else if (vars.maxSettleableDebt >= vars.scaledDeposit) { vars.collateralUsed = Maths.ceilWdiv(vars.scaledDeposit, vars.price); // subtract from debt the corresponding t0 amount of deposit remainingt0Debt_ -= Maths.floorWdiv(vars.scaledDeposit, inflator_); } // 3) loan's collateral can not cover remaining loan debt to settle, loan collateral is the constraint else { vars.unscaledDeposit = Maths.wdiv(vars.maxSettleableDebt, vars.scale); vars.collateralUsed = remainingCollateral_; remainingt0Debt_ -= Maths.floorWdiv(vars.maxSettleableDebt, inflator_); } // remove settled collateral from loan remainingCollateral_ -= vars.collateralUsed; // use HPB bucket to swap loan collateral for loan debt Bucket storage hpb = buckets_[vars.index]; vars.hpbLP = hpb.lps; vars.hpbCollateral = hpb.collateral + vars.collateralUsed; // set amount to remove as min of calculated amount and available deposit (to prevent rounding issues) vars.unscaledDeposit = Maths.min(vars.hpbUnscaledDeposit, vars.unscaledDeposit); vars.hpbUnscaledDeposit -= vars.unscaledDeposit; // remove amount to settle debt from bucket (could be entire deposit or only the settled debt) // when unscaledDeposit == 0 the amount of debt is very small and worth forgiving versus having settle revert if (vars.unscaledDeposit != 0) Deposits.unscaledRemove(deposits_, vars.index, vars.unscaledDeposit); // check if bucket healthy - set bankruptcy if collateral is 0 and entire deposit was used to settle and there's still LP if (vars.hpbCollateral == 0 && vars.hpbUnscaledDeposit == 0 && vars.hpbLP != 0) { hpb.lps = 0; hpb.bankruptcyTime = block.timestamp; emit BucketBankruptcy( vars.index, vars.hpbLP ); } else { // add settled collateral into bucket hpb.collateral = vars.hpbCollateral; } } else { // Deposits in the tree is zero, insert entire collateral into lowest bucket 7388 Buckets.addCollateral( buckets_[vars.index], params_.borrower, 0, // zero deposit in bucket remainingCollateral_, vars.price ); // entire collateral added into bucket, no borrower pledged collateral remaining remainingCollateral_ = 0; } --bucketDepth_; } } /** * @notice Called to forgive bad debt starting from next `HPB`, up to the number of remaining buckets depth. * @dev === Write state === * @dev - `Deposits.unscaledRemove()` (remove amount in `Fenwick` tree, from index): * @dev update `values` array state * @dev reset `bucket.lps` accumulator and update `bucket.bankruptcyTime` * @dev === Emit events === * @dev - `BucketBankruptcy` * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param params_ Struct containing params for settle action. * @param borrower_ Struct containing borrower details. * @param inflator_ Current pool inflator. * @return remainingt0Debt_ Remaining borrower `t0` debt after forgiving bad debt in case not enough buckets used. */ function _forgiveBadDebt( mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, SettleParams memory params_, Borrower memory borrower_, uint256 inflator_ ) internal returns (uint256 remainingt0Debt_) { remainingt0Debt_ = borrower_.t0Debt; // loop through remaining buckets if there's still debt to forgive while (params_.bucketDepth != 0 && remainingt0Debt_ != 0) { (uint256 index, , uint256 scale) = Deposits.findIndexAndSumOfSum(deposits_, 1); uint256 unscaledDeposit = Deposits.unscaledValueAt(deposits_, index); uint256 depositToRemove = Maths.wmul(scale, unscaledDeposit); uint256 debt = Maths.wmul(remainingt0Debt_, inflator_); uint256 depositRemaining; // 1) bucket deposit covers entire loan debt to settle, no constraints needed if (depositToRemove >= debt) { // no remaining debt to forgive remainingt0Debt_ = 0; uint256 depositUsed = Maths.min(Maths.wdiv(debt, scale), unscaledDeposit); depositRemaining = unscaledDeposit - depositUsed; // Remove deposit used to forgive bad debt from bucket // when depositUsed == 0 the amount of debt is very small and worth forgiving versus having settle revert if (depositUsed != 0) Deposits.unscaledRemove(deposits_, index, depositUsed); // 2) loan debt to settle exceeds bucket deposit, bucket deposit is the constraint } else { // subtract from remaining debt the corresponding t0 amount of deposit remainingt0Debt_ -= Maths.floorWdiv(depositToRemove, inflator_); // Remove all deposit from bucket // when unscaledDeposit == 0 the amount of debt is very small and worth forgiving versus having settle revert if (unscaledDeposit != 0) Deposits.unscaledRemove(deposits_, index, unscaledDeposit); } Bucket storage hpbBucket = buckets_[index]; uint256 bucketLP = hpbBucket.lps; // If the remaining deposit and resulting bucket collateral is so small that the exchange rate // rounds to 0, then bankrupt the bucket. Note that lhs are WADs, so the // quantity is naturally 1e18 times larger than the actual product if (depositRemaining * scale + hpbBucket.collateral * _priceAt(index) <= bucketLP) { // existing LP for the bucket shall become unclaimable hpbBucket.lps = 0; hpbBucket.bankruptcyTime = block.timestamp; emit BucketBankruptcy( index, bucketLP ); } --params_.bucketDepth; } } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { PoolType } from '../../interfaces/pool/IPool.sol'; import { AuctionsState, Borrower, Bucket, BurnEvent, DepositsState, Liquidation, LoansState, PoolState, ReserveAuctionState } from '../../interfaces/pool/commons/IPoolState.sol'; import { TakeResult } from '../../interfaces/pool/commons/IPoolInternals.sol'; import { _auctionPrice, _bpf, _priceAt, _reserveAuctionPrice, _roundToScale, _roundUpToScale } from '../helpers/PoolHelper.sol'; import { _revertOnMinDebt } from '../helpers/RevertsHelper.sol'; import { SettlerActions } from './SettlerActions.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Loans } from '../internal/Loans.sol'; import { Maths } from '../internal/Maths.sol'; /** @title Auction Taker Actions library @notice External library containing actions involving taking auctions within pool: - `take` and `bucketTake` auctioned collateral; take reserves */ library TakerActions { /*******************************/ /*** Function Params Structs ***/ /*******************************/ /// @dev Struct used to hold `bucketTake` function params. struct BucketTakeParams { address borrower; // borrower address to take from bool depositTake; // deposit or arb take, used by bucket take uint256 index; // bucket index, used by bucket take uint256 inflator; // [WAD] current pool inflator uint256 collateralScale; // precision of collateral token based on decimals } /// @dev Struct used to hold `take` function params. struct TakeParams { address borrower; // borrower address to take from uint256 takeCollateral; // [WAD] desired amount to take uint256 inflator; // [WAD] current pool inflator uint256 poolType; // pool type (ERC20 or NFT) uint256 collateralScale; // precision of collateral token based on decimals } /*************************/ /*** Local Var Structs ***/ /*************************/ /// @dev Struct used for `take` function local vars. struct TakeLocalVars { uint256 auctionPrice; // [WAD] The price of auction. uint256 bondChange; // [WAD] The change made on the bond size (beeing reward or penalty). uint256 borrowerDebt; // [WAD] The accrued debt of auctioned borrower. int256 bpf; // The bond penalty factor. uint256 bondFactor; // [WAD] The bond factor. uint256 bucketPrice; // [WAD] The bucket price. uint256 bucketScale; // [WAD] The bucket scale. uint256 collateralAmount; // [WAD] The amount of collateral taken. uint256 excessQuoteToken; // [WAD] Difference of quote token that borrower receives after take (for fractional NFT only) bool isRewarded; // True if kicker is rewarded (auction price lower than neutral price), false if penalized (auction price greater than neutral price). address kicker; // Address of auction kicker. uint256 quoteTokenAmount; // [WAD] Scaled quantity in Fenwick tree and before 1-bpf factor, paid for collateral uint256 t0RepayAmount; // [WAD] The amount of debt (quote tokens) that is recovered / repayed by take t0 terms. uint256 t0BorrowerDebt; // [WAD] Borrower's t0 debt. uint256 unscaledDeposit; // [WAD] Unscaled bucket quantity uint256 unscaledQuoteTokenAmount; // [WAD] The unscaled token amount that taker should pay for collateral taken. uint256 depositCollateralConstraint; // [WAD] Constraint on bucket take from deposit present in bucket uint256 debtCollateralConstraint; // [WAD] Constraint on take due to debt. } /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event BucketTake(address indexed borrower, uint256 index, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward); event BucketTakeLPAwarded(address indexed taker, address indexed kicker, uint256 lpAwardedTaker, uint256 lpAwardedKicker); event Take(address indexed borrower, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward); event ReserveAuction(uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error AuctionNotTakeable(); error AuctionPriceGtBucketPrice(); error CollateralRoundingNeededButNotPossible(); error InsufficientLiquidity(); error InsufficientCollateral(); error InvalidAmount(); error NoAuction(); error NoReservesAuction(); /***************************/ /*** External Functions ***/ /***************************/ /** * @notice See `IPoolTakerActions` for descriptions. * @notice Performs bucket take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). * @dev === Reverts on === * @dev not enough collateral to take `InsufficientCollateral()` * @return result_ `TakeResult` struct containing details of bucket take result. */ function bucketTake( AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, LoansState storage loans_, PoolState memory poolState_, address borrowerAddress_, bool depositTake_, uint256 index_, uint256 collateralScale_ ) external returns (TakeResult memory result_) { Borrower memory borrower = loans_.borrowers[borrowerAddress_]; // revert if borrower's collateral is 0 if (borrower.collateral == 0) revert InsufficientCollateral(); result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; // bucket take auction TakeLocalVars memory vars = _takeBucket( auctions_, buckets_, deposits_, borrower, BucketTakeParams({ borrower: borrowerAddress_, inflator: poolState_.inflator, depositTake: depositTake_, index: index_, collateralScale: collateralScale_ }) ); // update borrower after take borrower.collateral -= vars.collateralAmount; borrower.t0Debt = vars.t0BorrowerDebt - vars.t0RepayAmount; // update pool params after take poolState_.t0Debt -= vars.t0RepayAmount; poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // update loan after take ( result_.newLup, result_.settledAuction, result_.remainingCollateral, result_.compensatedCollateral ) = _takeLoan(auctions_, buckets_, deposits_, loans_, poolState_, borrower, borrowerAddress_); // complete take result struct result_.debtPostAction = borrower.t0Debt; result_.collateralPostAction = borrower.collateral; result_.t0PoolDebt = poolState_.t0Debt; result_.poolDebt = poolState_.debt; result_.collateralAmount = vars.collateralAmount; // if settled then debt in auction changed is the entire borrower debt, otherwise only repaid amount result_.t0DebtInAuctionChange = result_.settledAuction ? vars.t0BorrowerDebt : vars.t0RepayAmount; } /** * @notice See `IPoolTakerActions` for descriptions. * @notice Performs take collateral on an auction, rewards taker and kicker (if case) and updates loan info (settles auction if case). * @dev === Reverts on === * @dev insufficient collateral to take `InsufficientCollateral()` * @return result_ `TakeResult` struct containing details of take result. */ function take( AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, LoansState storage loans_, PoolState memory poolState_, address borrowerAddress_, uint256 collateral_, uint256 collateralScale_ ) external returns (TakeResult memory result_) { // revert if no amount to take if (collateral_ == 0) revert InvalidAmount(); Borrower memory borrower = loans_.borrowers[borrowerAddress_]; if ( // revert in case of NFT take when there isn't a full token to be taken (poolState_.poolType == uint8(PoolType.ERC721) && borrower.collateral < 1e18) || // revert in case of ERC20 take when no collateral to be taken (poolState_.poolType == uint8(PoolType.ERC20) && borrower.collateral == 0) ) { revert InsufficientCollateral(); } result_.debtPreAction = borrower.t0Debt; result_.collateralPreAction = borrower.collateral; // take auction TakeLocalVars memory vars = _take( auctions_, borrower, TakeParams({ borrower: borrowerAddress_, takeCollateral: collateral_, inflator: poolState_.inflator, poolType: poolState_.poolType, collateralScale: collateralScale_ }) ); // update borrower after take borrower.collateral -= vars.collateralAmount; borrower.t0Debt = vars.t0BorrowerDebt - vars.t0RepayAmount; // update pool params after take poolState_.t0Debt -= vars.t0RepayAmount; poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // update loan after take ( result_.newLup, result_.settledAuction, result_.remainingCollateral, result_.compensatedCollateral ) = _takeLoan(auctions_, buckets_, deposits_, loans_, poolState_, borrower, borrowerAddress_); // complete take result struct result_.debtPostAction = borrower.t0Debt; result_.collateralPostAction = borrower.collateral; result_.t0PoolDebt = poolState_.t0Debt; result_.poolDebt = poolState_.debt; result_.collateralAmount = vars.collateralAmount; result_.quoteTokenAmount = vars.quoteTokenAmount; result_.excessQuoteToken = vars.excessQuoteToken; // if settled then debt in auction changed is the entire borrower debt, otherwise only repaid amount result_.t0DebtInAuctionChange = result_.settledAuction ? vars.t0BorrowerDebt : vars.t0RepayAmount; } /*************************/ /*** Reserve Auction ***/ /*************************/ /** * @notice See `IPoolTakerActions` for descriptions. * @dev === Write state === * @dev decrement `reserveAuction.unclaimed` accumulator * @dev === Reverts on === * @dev not kicked or `72` hours passed `NoReservesAuction()` * @dev 0 take amount or 0 AJNA burned `InvalidAmount()` * @dev === Emit events === * @dev - `ReserveAuction` */ function takeReserves( ReserveAuctionState storage reserveAuction_, uint256 maxAmount_, uint256 quoteScale_ ) external returns (uint256 amount_, uint256 ajnaRequired_) { uint256 kicked = reserveAuction_.kicked; if (kicked != 0 && block.timestamp - kicked <= 72 hours) { uint256 unclaimed = reserveAuction_.unclaimed; uint256 price = _reserveAuctionPrice(kicked, reserveAuction_.lastKickedReserves); amount_ = Maths.min(unclaimed, maxAmount_); // revert if no amount to be taken if (amount_ / quoteScale_ == 0) revert InvalidAmount(); ajnaRequired_ = Maths.ceilWmul(amount_, price); // prevent 0-bid; must burn at least 1 wei of AJNA if (ajnaRequired_ == 0) revert InvalidAmount(); unclaimed -= amount_; reserveAuction_.unclaimed = unclaimed; uint256 totalBurned = reserveAuction_.totalAjnaBurned + ajnaRequired_; // accumulate additional ajna burned reserveAuction_.totalAjnaBurned = totalBurned; uint256 burnEventEpoch = reserveAuction_.latestBurnEventEpoch; // record burn event information to enable querying by staking rewards BurnEvent storage burnEvent = reserveAuction_.burnEvents[burnEventEpoch]; burnEvent.totalInterest = reserveAuction_.totalInterestEarned; burnEvent.totalBurned = totalBurned; emit ReserveAuction( unclaimed, price, burnEventEpoch ); } else { revert NoReservesAuction(); } } /**************************/ /*** Internal Functions ***/ /**************************/ /** * @notice Performs take collateral on an auction and updates bond size and kicker balance accordingly. * @dev === Emit events === * @dev - `Take` * @param auctions_ Struct for pool auctions state. * @param borrower_ Struct containing auctioned borrower details. * @param params_ Struct containing take action params details. * @return vars_ Struct containing auction take vars. */ function _take( AuctionsState storage auctions_, Borrower memory borrower_, TakeParams memory params_ ) internal returns (TakeLocalVars memory vars_) { Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; vars_ = _prepareTake( liquidation, 0, borrower_.t0Debt, params_.inflator ); // These are placeholder max values passed to calculateTakeFlows because there is no explicit bound on the // quote token amount in take calls (as opposed to bucketTake) vars_.unscaledDeposit = type(uint256).max; vars_.bucketScale = Maths.WAD; uint256 takeableCollateral = borrower_.collateral; // for NFT take make sure the take flow and bond change calculation happens for the rounded collateral that can be taken if (params_.poolType == uint8(PoolType.ERC721)) { takeableCollateral = (takeableCollateral / 1e18) * 1e18; } // In the case of take, the taker binds the collateral qty but not the quote token qty // ugly to get take work like a bucket take -- this is the max amount of quote token from the take that could go to // reduce the debt of the borrower -- analagous to the amount of deposit in the bucket for a bucket take vars_ = _calculateTakeFlowsAndBondChange( Maths.min(takeableCollateral, params_.takeCollateral), params_.inflator, params_.collateralScale, vars_ ); _rewardTake(auctions_, liquidation, vars_); if (params_.poolType == uint8(PoolType.ERC721)) { // slither-disable-next-line divide-before-multiply uint256 collateralTaken = (vars_.collateralAmount / 1e18) * 1e18; // solidity rounds down, so if 2.5 it will be 2.5 / 1 = 2 // collateral taken not a round number if (collateralTaken != vars_.collateralAmount) { if (Maths.min(borrower_.collateral, params_.takeCollateral) >= collateralTaken + 1e18) { // round up collateral to take collateralTaken += 1e18; // taker should send additional quote tokens to cover difference between collateral needed to be taken and rounded collateral, at auction price // borrower will get quote tokens for the difference between rounded collateral and collateral taken to cover debt vars_.excessQuoteToken = Maths.wmul(collateralTaken - vars_.collateralAmount, vars_.auctionPrice); vars_.collateralAmount = collateralTaken; } else { // shouldn't get here, but just in case revert revert CollateralRoundingNeededButNotPossible(); } } } emit Take( params_.borrower, vars_.quoteTokenAmount, vars_.collateralAmount, vars_.bondChange, vars_.isRewarded ); } /** * @notice Performs bucket take collateral on an auction and rewards taker and kicker (if case). * @dev === Emit events === * @dev - `BucketTake` * @param auctions_ Struct for pool auctions state. * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param borrower_ Struct containing auctioned borrower details. * @param params_ Struct containing take action details. * @return vars_ Struct containing auction take vars. */ function _takeBucket( AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, Borrower memory borrower_, BucketTakeParams memory params_ ) internal returns (TakeLocalVars memory vars_) { Liquidation storage liquidation = auctions_.liquidations[params_.borrower]; vars_= _prepareTake( liquidation, _priceAt(params_.index), borrower_.t0Debt, params_.inflator ); vars_.unscaledDeposit = Deposits.unscaledValueAt(deposits_, params_.index); // revert if no quote tokens in arbed bucket if (vars_.unscaledDeposit == 0) revert InsufficientLiquidity(); // cannot arb with a price lower than the auction price if (vars_.auctionPrice > vars_.bucketPrice) revert AuctionPriceGtBucketPrice(); // if deposit take then price to use when calculating take is bucket price if (params_.depositTake) vars_.auctionPrice = vars_.bucketPrice; vars_.bucketScale = Deposits.scale(deposits_, params_.index); vars_ = _calculateTakeFlowsAndBondChange( borrower_.collateral, params_.inflator, params_.collateralScale, vars_ ); // revert if bucket deposit cannot cover at least one unit of collateral if (vars_.collateralAmount == 0) revert InsufficientLiquidity(); _rewardBucketTake( auctions_, deposits_, buckets_, liquidation, params_.index, params_.depositTake, vars_ ); emit BucketTake( params_.borrower, params_.index, vars_.quoteTokenAmount, vars_.collateralAmount, vars_.bondChange, vars_.isRewarded ); } /** * @notice Performs update of an auctioned loan that was taken (using bucket or regular take). * @notice If borrower's debt has been fully covered, then auction is settled. Update loan's state. * @dev === Reverts on === * @dev borrower debt less than pool min debt `AmountLTMinDebt()` * @param auctions_ Struct for pool auctions state. * @param buckets_ Struct for pool buckets state. * @param deposits_ Struct for pool deposits state. * @param loans_ Struct for pool loans state. * @param poolState_ Struct containing pool details. * @param borrower_ The borrower details owning loan that is taken. * @param borrowerAddress_ The address of the borrower. * @return newLup_ The new `LUP` of pool (after debt is repaid). * @return settledAuction_ True if auction is settled by the take action. (`NFT` take: rebalance borrower collateral in pool if true) * @return remainingCollateral_ Borrower collateral remaining after take action. (`NFT` take: collateral to be rebalanced in case of `NFT` settlement) * @return compensatedCollateral_ Amount of collateral compensated, to be deducted from pool pledged collateral accumulator. */ function _takeLoan( AuctionsState storage auctions_, mapping(uint256 => Bucket) storage buckets_, DepositsState storage deposits_, LoansState storage loans_, PoolState memory poolState_, Borrower memory borrower_, address borrowerAddress_ ) internal returns ( uint256 newLup_, bool settledAuction_, uint256 remainingCollateral_, uint256 compensatedCollateral_ ) { uint256 borrowerDebt = Maths.wmul(borrower_.t0Debt, poolState_.inflator); // check that taking from loan doesn't leave borrower debt under min debt amount _revertOnMinDebt( loans_, poolState_.debt, borrowerDebt, poolState_.quoteTokenScale ); remainingCollateral_ = borrower_.collateral; // if debt is fully repaid, settle the auction if (borrower_.t0Debt == 0) { settledAuction_ = true; // settle auction and update borrower's collateral with value after settlement (remainingCollateral_, compensatedCollateral_) = SettlerActions._settleAuction( auctions_, buckets_, deposits_, borrowerAddress_, borrower_.collateral, poolState_.poolType ); borrower_.collateral = remainingCollateral_; } // update loan state, stamp borrower Np to Tp ratio only when exiting from auction Loans.update( loans_, borrower_, borrowerAddress_, poolState_.rate, !settledAuction_, settledAuction_ // stamp borrower Np to Tp ratio if exiting from auction ); // calculate new lup with repaid debt from take newLup_ = Deposits.getLup(deposits_, poolState_.debt); } /** * @notice Rewards actors of a regular take action. * @dev === Write state === * @dev update liquidation `bond size` accumulator * @dev update kicker's `locked balance` accumulator * @dev update `auctions.totalBondEscrowed` accumulator * @param auctions_ Struct for pool auctions state. * @param liquidation_ Struct containing details of auction. * @param vars Struct containing take action result details. */ function _rewardTake( AuctionsState storage auctions_, Liquidation storage liquidation_, TakeLocalVars memory vars ) internal { if (vars.isRewarded) { // take is below neutralPrice, Kicker is rewarded liquidation_.bondSize += SafeCast.toUint160(vars.bondChange); auctions_.kickers[vars.kicker].locked += vars.bondChange; auctions_.totalBondEscrowed += vars.bondChange; } else { // take is above neutralPrice, Kicker is penalized vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); liquidation_.bondSize -= SafeCast.toUint160(vars.bondChange); auctions_.kickers[vars.kicker].locked -= vars.bondChange; auctions_.totalBondEscrowed -= vars.bondChange; } } /** * @notice Rewards actors of a bucket take action. * @dev === Write state === * @dev - `Buckets.addLenderLP`: * @dev increment taker `lender.lps` accumulator and `lender.depositTime` state * @dev increment kicker `lender.lps` accumulator and l`ender.depositTime` state * @dev - update liquidation bond size accumulator * @dev - update kicker's locked balance accumulator * @dev - update `auctions.totalBondEscrowed` accumulator * @dev - `Deposits.unscaledRemove()` (remove amount in `Fenwick` tree, from index): * @dev update `values` array state * @dev - increment `bucket.collateral` and `bucket.lps` accumulator * @dev === Emit events === * @dev - `BucketTakeLPAwarded` * @dev === Reverts on === * @dev calculated unscaled amount to remove is 0 `InvalidAmount()` * @param auctions_ Struct for pool auctions state. * @param deposits_ Struct for pool deposits state. * @param buckets_ Struct for pool buckets state. * @param liquidation_ Struct containing details of auction to be taken from. * @param bucketIndex_ Index of a bucket, likely the `HPB`, in which collateral will be deposited. * @param depositTake_ If `true` then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. * @param vars Struct containing bucket take action result details. */ function _rewardBucketTake( AuctionsState storage auctions_, DepositsState storage deposits_, mapping(uint256 => Bucket) storage buckets_, Liquidation storage liquidation_, uint256 bucketIndex_, bool depositTake_, TakeLocalVars memory vars ) internal { Bucket storage bucket = buckets_[bucketIndex_]; uint256 bankruptcyTime = bucket.bankruptcyTime; uint256 scaledDeposit = Maths.wmul(vars.unscaledDeposit, vars.bucketScale); uint256 totalLPReward; uint256 takerLPReward; uint256 kickerLPReward; // if arb take - taker is awarded collateral * (bucket price - auction price) worth (in quote token terms) units of LPB in the bucket if (!depositTake_) { takerLPReward = Buckets.quoteTokensToLP( bucket.collateral, bucket.lps, scaledDeposit, Maths.wmul(vars.collateralAmount, vars.bucketPrice - vars.auctionPrice), vars.bucketPrice, Math.Rounding.Down ); totalLPReward = takerLPReward; Buckets.addLenderLP(bucket, bankruptcyTime, msg.sender, takerLPReward); } // the bondholder/kicker is awarded bond change worth of LPB in the bucket if (vars.isRewarded) { kickerLPReward = Buckets.quoteTokensToLP( bucket.collateral, bucket.lps, scaledDeposit, vars.bondChange, vars.bucketPrice, Math.Rounding.Down ); totalLPReward += kickerLPReward; Buckets.addLenderLP(bucket, bankruptcyTime, vars.kicker, kickerLPReward); } else { // take is above neutralPrice, Kicker is penalized vars.bondChange = Maths.min(liquidation_.bondSize, vars.bondChange); liquidation_.bondSize -= SafeCast.toUint160(vars.bondChange); auctions_.kickers[vars.kicker].locked -= vars.bondChange; auctions_.totalBondEscrowed -= vars.bondChange; } // remove quote tokens from bucket’s deposit Deposits.unscaledRemove(deposits_, bucketIndex_, vars.unscaledQuoteTokenAmount); // total rewarded LP are added to the bucket LP balance if (totalLPReward != 0) bucket.lps += totalLPReward; // collateral is added to the bucket’s claimable collateral bucket.collateral += vars.collateralAmount; emit BucketTakeLPAwarded( msg.sender, vars.kicker, takerLPReward, kickerLPReward ); } /** * @notice Utility function to validate take and calculate take's parameters. * @dev reverts on: * - loan is not in auction NoAuction() * @param liquidation_ Liquidation struct holding auction details. * @param bucketPrice_ Price of the bucket, or 0 for non-bucket takes. * @param t0Debt_ Borrower t0 debt. * @param inflator_ The pool's inflator, used to calculate borrower debt. * @return vars The prepared vars for take action. */ function _prepareTake( Liquidation memory liquidation_, uint256 bucketPrice_, uint256 t0Debt_, uint256 inflator_ ) internal view returns (TakeLocalVars memory vars) { uint256 kickTime = liquidation_.kickTime; if (kickTime == 0) revert NoAuction(); // Auction may not be taken in the same block it was kicked if (kickTime == block.timestamp) revert AuctionNotTakeable(); vars.t0BorrowerDebt = t0Debt_; vars.borrowerDebt = Maths.wmul(vars.t0BorrowerDebt, inflator_); uint256 neutralPrice = liquidation_.neutralPrice; vars.auctionPrice = _auctionPrice(liquidation_.referencePrice, kickTime); vars.bucketPrice = bucketPrice_; vars.bondFactor = liquidation_.bondFactor; vars.bpf = _bpf( liquidation_.debtToCollateral, neutralPrice, liquidation_.bondFactor, bucketPrice_ == 0 ? vars.auctionPrice : bucketPrice_ ); vars.kicker = liquidation_.kicker; vars.isRewarded = (vars.bpf >= 0); } /** * @notice Computes the flows of collateral, quote token between the borrower, lender and kicker. * @param totalCollateral_ Total collateral in loan. * @param inflator_ Current pool inflator. * @param vars TakeParams for the take/buckettake */ function _calculateTakeFlowsAndBondChange( uint256 totalCollateral_, uint256 inflator_, uint256 collateralScale_, TakeLocalVars memory vars ) internal pure returns ( TakeLocalVars memory ) { // price is the current auction price, which is the price paid by the LENDER for collateral // from the borrower point of view, there is a take penalty of (1.25 * bondFactor - 0.25 * bpf) // Therefore the price is actually price * (1.0 - 1.25 * bondFactor + 0.25 * bpf) uint256 takePenaltyFactor = uint256(5 * int256(vars.bondFactor) - vars.bpf + 3) / 4; // Round up uint256 borrowerPrice = Maths.floorWmul(vars.auctionPrice, Maths.WAD - takePenaltyFactor); // To determine the value of quote token removed from a bucket in a bucket take call, we need to account for whether the bond is // rewarded or not. If the bond is rewarded, we need to remove the bond reward amount from the amount removed, else it's simply the // collateral times auction price. uint256 netRewardedPrice = (vars.isRewarded) ? Maths.wmul(Maths.WAD - uint256(vars.bpf), vars.auctionPrice) : vars.auctionPrice; // auctions may not be zero-bid; prevent divide-by-zero in constraint calculations if (vars.auctionPrice == 0) revert InvalidAmount(); // Collateral taken in bucket takes is constrained by the deposit available at the price including the reward. This is moot in the case of takes. vars.depositCollateralConstraint = (vars.unscaledDeposit != type(uint256).max) ? _roundToScale(Math.mulDiv(vars.unscaledDeposit, vars.bucketScale, netRewardedPrice), collateralScale_) : type(uint256).max; // Collateral taken is also constained by the borrower's debt, at the price they receive. vars.debtCollateralConstraint = borrowerPrice != 0 ? _roundUpToScale(Maths.ceilWdiv(vars.borrowerDebt, borrowerPrice), collateralScale_) : type(uint256).max; if (vars.depositCollateralConstraint <= vars.debtCollateralConstraint && vars.depositCollateralConstraint <= totalCollateral_) { // quote token used to purchase is constraining factor vars.collateralAmount = vars.depositCollateralConstraint; vars.quoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); vars.t0RepayAmount = Math.mulDiv(vars.collateralAmount, borrowerPrice, inflator_); vars.unscaledQuoteTokenAmount = Maths.min( vars.unscaledDeposit, Math.mulDiv(vars.collateralAmount, netRewardedPrice, vars.bucketScale) ); } else if (vars.debtCollateralConstraint <= totalCollateral_) { // borrower debt is constraining factor vars.collateralAmount = vars.debtCollateralConstraint; vars.t0RepayAmount = vars.t0BorrowerDebt; vars.unscaledQuoteTokenAmount = Math.mulDiv(vars.collateralAmount, netRewardedPrice, vars.bucketScale); vars.quoteTokenAmount = Math.mulDiv(vars.collateralAmount, borrowerPrice, Maths.WAD - takePenaltyFactor); } else { // collateral available is constraint vars.collateralAmount = totalCollateral_; vars.t0RepayAmount = Math.mulDiv(totalCollateral_, borrowerPrice, inflator_); vars.unscaledQuoteTokenAmount = Math.mulDiv(totalCollateral_, netRewardedPrice, vars.bucketScale); vars.quoteTokenAmount = Maths.wmul(vars.collateralAmount, vars.auctionPrice); } // repaid amount cannot exceed the borrower owned debt vars.t0RepayAmount = Maths.min(vars.t0RepayAmount, vars.t0BorrowerDebt); if (vars.isRewarded) { // take is below neutralPrice, Kicker is rewarded vars.bondChange = Maths.floorWmul(vars.quoteTokenAmount, uint256(vars.bpf)); } else { // take is above neutralPrice, Kicker is penalized vars.bondChange = Maths.ceilWmul(vars.quoteTokenAmount, uint256(-vars.bpf)); } return vars; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title ERC20 Pool Borrower Actions */ interface IERC20PoolBorrowerActions { /** * @notice Called by borrowers to add collateral to the pool and/or borrow quote from the pool. * @dev Can be called by borrowers with either `0` `amountToBorrow_` or `0` `collateralToPledge_`, if borrower only wants to take a single action. * @param borrowerAddress_ The borrower to whom collateral was pledged, and/or debt was drawn for. * @param amountToBorrow_ The amount of quote tokens to borrow (`WAD` precision). * @param limitIndex_ Lower bound of `LUP` change (if any) that the borrower will tolerate from a creating or modifying position. * @param collateralToPledge_ The amount of collateral to be added to the pool (`WAD` precision). */ function drawDebt( address borrowerAddress_, uint256 amountToBorrow_, uint256 limitIndex_, uint256 collateralToPledge_ ) external; /** * @notice Called by borrowers to repay borrowed quote to the pool, and/or pull collateral form the pool. * @dev Can be called by borrowers with either `0` `maxQuoteTokenAmountToRepay_` or `0` `collateralAmountToPull_`, if borrower only wants to take a single action. * @param borrowerAddress_ The borrower whose loan is being interacted with. * @param maxQuoteTokenAmountToRepay_ The max amount of quote tokens to repay (`WAD` precision). * @param collateralAmountToPull_ The max amount of collateral to be puled from the pool (`WAD` precision). * @param recipient_ The address to receive amount of pulled collateral. * @param limitIndex_ Ensures `LUP` has not moved far from state when borrower pulls collateral. * @return amountRepaid_ The amount of quote token repaid (`WAD` precision). */ function repayDebt( address borrowerAddress_, uint256 maxQuoteTokenAmountToRepay_, uint256 collateralAmountToPull_, address recipient_, uint256 limitIndex_ ) external returns (uint256 amountRepaid_); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title ERC20 Pool Lender Actions */ interface IERC20PoolLenderActions { /** * @notice Deposit claimable collateral into a specified bucket. * @param amountToAdd_ Amount of collateral to deposit (`WAD` precision). * @param index_ The bucket index to which collateral will be deposited. * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. * @return bucketLP_ The amount of `LP` awarded for the added collateral (`WAD` precision). */ function addCollateral( uint256 amountToAdd_, uint256 index_, uint256 expiry_ ) external returns (uint256 bucketLP_); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title ERC20 Pool Immutables */ interface IERC20PoolImmutables { /** * @notice Returns the `collateralScale` immutable. * @return The precision of the collateral `ERC20` token based on decimals. */ function collateralScale() external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title ERC20 Pool Events */ interface IERC20PoolEvents { /** * @notice Emitted when actor adds claimable collateral to a bucket. * @param actor Recipient that added collateral. * @param index Index at which collateral were added. * @param amount Amount of collateral added to the pool (`WAD` precision). * @param lpAwarded Amount of `LP` awarded for the deposit (`WAD` precision). */ event AddCollateral( address indexed actor, uint256 indexed index, uint256 amount, uint256 lpAwarded ); /** * @notice Emitted when borrower draws debt from the pool, or adds collateral to the pool. * @param borrower The borrower to whom collateral was pledged, and/or debt was drawn for. * @param amountBorrowed Amount of quote tokens borrowed from the pool (`WAD` precision). * @param collateralPledged Amount of collateral locked in the pool (`WAD` precision). * @param lup `LUP` after borrow. */ event DrawDebt( address indexed borrower, uint256 amountBorrowed, uint256 collateralPledged, uint256 lup ); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Borrower Actions */ interface IPoolBorrowerActions { /** * @notice Called by fully collateralized borrowers to restamp the `Np to Tp ratio` of the loan (only if loan is fully collateralized and not in auction). * The reason for stamping the `Np to Tp ratio` on the loan is to provide some certainty to the borrower as to at what price they can expect to be liquidated. * This action can restamp only the loan of `msg.sender`. */ function stampLoan() external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool `LP` Actions */ interface IPoolLPActions { /** * @notice Called by `LP` owners to approve transfer of an amount of `LP` to a new owner. * @dev Intended for use by the `PositionManager` contract. * @param spender_ The new owner of the `LP`. * @param indexes_ Bucket indexes from where `LP` are transferred. * @param amounts_ The amounts of `LP` approved to transfer (`WAD` precision). */ function increaseLPAllowance( address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external; /** * @notice Called by `LP` owners to decrease the amount of `LP` that can be spend by a new owner. * @dev Intended for use by the `PositionManager` contract. * @param spender_ The new owner of the `LP`. * @param indexes_ Bucket indexes from where `LP` are transferred. * @param amounts_ The amounts of `LP` disapproved to transfer (`WAD` precision). */ function decreaseLPAllowance( address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external; /** * @notice Called by `LP` owners to decrease the amount of `LP` that can be spend by a new owner. * @param spender_ Address that is having it's allowance revoked. * @param indexes_ List of bucket index to remove the allowance from. */ function revokeLPAllowance( address spender_, uint256[] calldata indexes_ ) external; /** * @notice Called by `LP` owners to allow addresses that can transfer LP. * @dev Intended for use by the `PositionManager` contract. * @param transferors_ Addresses that are allowed to transfer `LP` to new owner. */ function approveLPTransferors( address[] calldata transferors_ ) external; /** * @notice Called by `LP` owners to revoke addresses that can transfer `LP`. * @dev Intended for use by the `PositionManager` contract. * @param transferors_ Addresses that are revoked to transfer `LP` to new owner. */ function revokeLPTransferors( address[] calldata transferors_ ) external; /** * @notice Called by `LP` owners to transfers their `LP` to a different address. `approveLpOwnership` needs to be run first. * @dev Used by `PositionManager.memorializePositions()`. * @param owner_ The original owner address of the position. * @param newOwner_ The new owner address of the position. * @param indexes_ Array of price buckets index at which `LP` were moved. */ function transferLP( address owner_, address newOwner_, uint256[] calldata indexes_ ) external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Lender Actions */ interface IPoolLenderActions { /*********************************************/ /*** Quote/collateral management functions ***/ /*********************************************/ /** * @notice Called by lenders to add an amount of credit at a specified price bucket. * @param amount_ The amount of quote token to be added by a lender (`WAD` precision). * @param index_ The index of the bucket to which the quote tokens will be added. * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. * @return bucketLP_ The amount of `LP` changed for the added quote tokens (`WAD` precision). * @return addedAmount_ The amount of quote token added (`WAD` precision). */ function addQuoteToken( uint256 amount_, uint256 index_, uint256 expiry_ ) external returns (uint256 bucketLP_, uint256 addedAmount_); /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket. * @param maxAmount_ The maximum amount of quote token to be moved by a lender (`WAD` precision). * @param fromIndex_ The bucket index from which the quote tokens will be removed. * @param toIndex_ The bucket index to which the quote tokens will be added. * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. * @return fromBucketLP_ The amount of `LP` moved out from bucket (`WAD` precision). * @return toBucketLP_ The amount of `LP` moved to destination bucket (`WAD` precision). * @return movedAmount_ The amount of quote token moved (`WAD` precision). */ function moveQuoteToken( uint256 maxAmount_, uint256 fromIndex_, uint256 toIndex_, uint256 expiry_ ) external returns (uint256 fromBucketLP_, uint256 toBucketLP_, uint256 movedAmount_); /** * @notice Called by lenders to claim collateral from a price bucket. * @param maxAmount_ The amount of collateral (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools) to claim. * @param index_ The bucket index from which collateral will be removed. * @return removedAmount_ The amount of collateral removed (`WAD` precision). * @return redeemedLP_ The amount of `LP` used for removing collateral amount (`WAD` precision). */ function removeCollateral( uint256 maxAmount_, uint256 index_ ) external returns (uint256 removedAmount_, uint256 redeemedLP_); /** * @notice Called by lenders to remove an amount of credit at a specified price bucket. * @param maxAmount_ The max amount of quote token to be removed by a lender (`WAD` precision). * @param index_ The bucket index from which quote tokens will be removed. * @return removedAmount_ The amount of quote token removed (`WAD` precision). * @return redeemedLP_ The amount of `LP` used for removing quote tokens amount (`WAD` precision). */ function removeQuoteToken( uint256 maxAmount_, uint256 index_ ) external returns (uint256 removedAmount_, uint256 redeemedLP_); /********************************/ /*** Interest update function ***/ /********************************/ /** * @notice Called by actors to update pool interest rate (can be updated only once in a `12` hours period of time). */ function updateInterest() external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Kicker Actions */ interface IPoolKickerActions { /********************/ /*** Liquidations ***/ /********************/ /** * @notice Called by actors to initiate a liquidation. * @param borrower_ Identifies the loan to liquidate. * @param npLimitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction. */ function kick( address borrower_, uint256 npLimitIndex_ ) external; /** * @notice Called by lenders to liquidate the top loan. * @param index_ The deposit index to use for kicking the top loan. * @param npLimitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction. */ function lenderKick( uint256 index_, uint256 npLimitIndex_ ) external; /** * @notice Called by kickers to withdraw their auction bonds (the amount of quote tokens that are not locked in active auctions). * @param recipient_ Address to receive claimed bonds amount. * @param maxAmount_ The max amount to withdraw from auction bonds (`WAD` precision). Constrained by claimable amounts and liquidity. * @return withdrawnAmount_ The amount withdrawn (`WAD` precision). */ function withdrawBonds( address recipient_, uint256 maxAmount_ ) external returns (uint256 withdrawnAmount_); /***********************/ /*** Reserve Auction ***/ /***********************/ /** * @notice Called by actor to start a `Claimable Reserve Auction` (`CRA`). */ function kickReserveAuction() external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Taker Actions */ interface IPoolTakerActions { /** * @notice Called by actors to use quote token to arb higher-priced deposit off the book. * @param borrowerAddress_ Address of the borower take is being called upon. * @param depositTake_ If `true` then the take will happen at an auction price equal with bucket price. Auction price is used otherwise. * @param index_ Index of a bucket, likely the `HPB`, in which collateral will be deposited. */ function bucketTake( address borrowerAddress_, bool depositTake_, uint256 index_ ) external; /** * @notice Called by actors to purchase collateral from the auction in exchange for quote token. * @param borrowerAddress_ Address of the borower take is being called upon. * @param maxAmount_ Max amount of collateral that will be taken from the auction (`WAD` precision for `ERC20` pools, max number of `NFT`s for `ERC721` pools). * @param callee_ Identifies where collateral should be sent and where quote token should be obtained. * @param data_ If provided, take will assume the callee implements `IERC*Taker`. Take will send collateral to * callee before passing this data to `IERC*Taker.atomicSwapCallback`. If not provided, * the callback function will not be invoked. * @return collateralTaken_ Amount of collateral taken from the auction (`WAD` precision for `ERC20` pools, max number of `NFT`s for `ERC721` pools). */ function take( address borrowerAddress_, uint256 maxAmount_, address callee_, bytes calldata data_ ) external returns (uint256 collateralTaken_); /***********************/ /*** Reserve Auction ***/ /***********************/ /** * @notice Purchases claimable reserves during a `CRA` using `Ajna` token. * @param maxAmount_ Maximum amount of quote token to purchase at the current auction price (`WAD` precision). * @return amount_ Actual amount of reserves taken (`WAD` precision). */ function takeReserves( uint256 maxAmount_ ) external returns (uint256 amount_); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Settler Actions */ interface IPoolSettlerActions { /** * @notice Called by actors to settle an amount of debt in a completed liquidation. * @param borrowerAddress_ Address of the auctioned borrower. * @param maxDepth_ Measured from `HPB`, maximum number of buckets deep to settle debt. * @return collateralSettled_ Amount of collateral settled. * @return isBorrowerSettled_ True if all borrower's debt is settled. * @dev `maxDepth_` is used to prevent unbounded iteration clearing large liquidations. */ function settle( address borrowerAddress_, uint256 maxDepth_ ) external returns (uint256 collateralSettled_, bool isBorrowerSettled_); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Immutables */ interface IPoolImmutables { /** * @notice Returns the type of the pool (`0` for `ERC20`, `1` for `ERC721`). */ function poolType() external pure returns (uint8); /** * @notice Returns the address of the pool's collateral token. */ function collateralAddress() external pure returns (address); /** * @notice Returns the address of the pool's quote token. */ function quoteTokenAddress() external pure returns (address); /** * @notice Returns the `quoteTokenScale` state variable. * @notice Token scale is also the minimum amount a lender may have in a bucket (dust amount). * @return The precision of the quote `ERC20` token based on decimals. */ function quoteTokenScale() external pure returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Derived State */ interface IPoolDerivedState { /** * @notice Returns the exchange rate for a given bucket index. * @param index_ The bucket index. * @return exchangeRate_ Exchange rate of the bucket (`WAD` precision). */ function bucketExchangeRate( uint256 index_ ) external view returns (uint256 exchangeRate_); /** * @notice Returns the prefix sum of a given bucket. * @param index_ The bucket index. * @return The deposit up to given index (`WAD` precision). */ function depositUpToIndex( uint256 index_ ) external view returns (uint256); /** * @notice Returns the bucket index for a given debt amount. * @param debt_ The debt amount to calculate bucket index for (`WAD` precision). * @return Bucket index. */ function depositIndex( uint256 debt_ ) external view returns (uint256); /** * @notice Returns the total amount of quote tokens deposited in pool. * @return Total amount of deposited quote tokens (`WAD` precision). */ function depositSize() external view returns (uint256); /** * @notice Returns the meaningful actual utilization of the pool. * @return Deposit utilization (`WAD` precision). */ function depositUtilization() external view returns (uint256); /** * @notice Returns the scaling value of deposit at given index. * @param index_ Deposit index. * @return Deposit scaling (`WAD` precision). */ function depositScale( uint256 index_ ) external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Events */ interface IPoolEvents { /*********************/ /*** Lender events ***/ /*********************/ /** * @notice Emitted when lender adds quote token to the pool. * @param lender Recipient that added quote tokens. * @param index Index at which quote tokens were added. * @param amount Amount of quote tokens added to the pool (`WAD` precision). * @param lpAwarded Amount of `LP` awarded for the deposit (`WAD` precision). * @param lup `LUP` calculated after deposit. */ event AddQuoteToken( address indexed lender, uint256 indexed index, uint256 amount, uint256 lpAwarded, uint256 lup ); /** * @notice Emitted when lender moves quote token from a bucket price to another. * @param lender Recipient that moved quote tokens. * @param from Price bucket from which quote tokens were moved. * @param to Price bucket where quote tokens were moved. * @param amount Amount of quote tokens moved (`WAD` precision). * @param lpRedeemedFrom Amount of `LP` removed from the `from` bucket (`WAD` precision). * @param lpAwardedTo Amount of `LP` credited to the `to` bucket (`WAD` precision). * @param lup `LUP` calculated after removal. */ event MoveQuoteToken( address indexed lender, uint256 indexed from, uint256 indexed to, uint256 amount, uint256 lpRedeemedFrom, uint256 lpAwardedTo, uint256 lup ); /** * @notice Emitted when lender removes quote token from the pool. * @param lender Recipient that removed quote tokens. * @param index Index at which quote tokens were removed. * @param amount Amount of quote tokens removed from the pool (`WAD` precision). * @param lpRedeemed Amount of `LP` exchanged for quote token (`WAD` precision). * @param lup `LUP` calculated after removal. */ event RemoveQuoteToken( address indexed lender, uint256 indexed index, uint256 amount, uint256 lpRedeemed, uint256 lup ); /** * @notice Emitted when lender claims collateral from a bucket. * @param claimer Recipient that claimed collateral. * @param index Index at which collateral was claimed. * @param amount The amount of collateral (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools) transferred to the claimer. * @param lpRedeemed Amount of `LP` exchanged for quote token (`WAD` precision). */ event RemoveCollateral( address indexed claimer, uint256 indexed index, uint256 amount, uint256 lpRedeemed ); /***********************/ /*** Borrower events ***/ /***********************/ /** * @notice Emitted when borrower repays quote tokens to the pool and/or pulls collateral from the pool. * @param borrower `msg.sender` or on behalf of sender. * @param quoteRepaid Amount of quote tokens repaid to the pool (`WAD` precision). * @param collateralPulled The amount of collateral (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools) transferred to the claimer. * @param lup `LUP` after repay. */ event RepayDebt( address indexed borrower, uint256 quoteRepaid, uint256 collateralPulled, uint256 lup ); /**********************/ /*** Auction events ***/ /**********************/ /** * @notice Emitted when a liquidation is initiated. * @param borrower Identifies the loan being liquidated. * @param debt Debt the liquidation will attempt to cover (`WAD` precision). * @param collateral Amount of collateral up for liquidation (`WAD` precision for `ERC20` pools, number of `NFT` tokens for `ERC721` pools). * @param bond Bond amount locked by kicker (`WAD` precision). */ event Kick( address indexed borrower, uint256 debt, uint256 collateral, uint256 bond ); /** * @notice Emitted when kickers are withdrawing funds posted as auction bonds. * @param kicker The kicker withdrawing bonds. * @param reciever The address receiving withdrawn bond amount. * @param amount The bond amount that was withdrawn (`WAD` precision). */ event BondWithdrawn( address indexed kicker, address indexed reciever, uint256 amount ); /** * @notice Emitted when an actor uses quote token to arb higher-priced deposit off the book. * @param borrower Identifies the loan being liquidated. * @param index The index of the `Highest Price Bucket` used for this take. * @param amount Amount of quote token used to purchase collateral (`WAD` precision). * @param collateral Amount of collateral purchased with quote token (`WAD` precision). * @param bondChange Impact of this take to the liquidation bond (`WAD` precision). * @param isReward `True` if kicker was rewarded with `bondChange` amount, `false` if kicker was penalized. * @dev amount / collateral implies the auction price. */ event BucketTake( address indexed borrower, uint256 index, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward ); /** * @notice Emitted when `LP` are awarded to a taker or kicker in a bucket take. * @param taker Actor who invoked the bucket take. * @param kicker Actor who started the auction. * @param lpAwardedTaker Amount of `LP` awarded to the taker (`WAD` precision). * @param lpAwardedKicker Amount of `LP` awarded to the actor who started the auction (`WAD` precision). */ event BucketTakeLPAwarded( address indexed taker, address indexed kicker, uint256 lpAwardedTaker, uint256 lpAwardedKicker ); /** * @notice Emitted when an actor uses quote token outside of the book to purchase collateral under liquidation. * @param borrower Identifies the loan being liquidated. * @param amount Amount of quote token used to purchase collateral (`WAD` precision). * @param collateral Amount of collateral purchased with quote token (for `ERC20` pool, `WAD` precision) or number of `NFT`s purchased (for `ERC721` pool). * @param bondChange Impact of this take to the liquidation bond (`WAD` precision). * @param isReward `True` if kicker was rewarded with `bondChange` amount, `false` if kicker was penalized. * @dev amount / collateral implies the auction price. */ event Take( address indexed borrower, uint256 amount, uint256 collateral, uint256 bondChange, bool isReward ); /** * @notice Emitted when an actor settles debt in a completed liquidation * @param borrower Identifies the loan under liquidation. * @param settledDebt Amount of pool debt settled in this transaction (`WAD` precision). * @dev When `amountRemaining_ == 0`, the auction has been completed cleared and removed from the queue. */ event Settle( address indexed borrower, uint256 settledDebt ); /** * @notice Emitted when auction is completed. * @param borrower Address of borrower that exits auction. * @param collateral Borrower's remaining collateral when auction completed (`WAD` precision). */ event AuctionSettle( address indexed borrower, uint256 collateral ); /** * @notice Emitted when `NFT` auction is completed. * @param borrower Address of borrower that exits auction. * @param collateral Borrower's remaining collateral when auction completed. * @param lp Amount of `LP` given to the borrower to compensate fractional collateral (if any, `WAD` precision). * @param index Index of the bucket with `LP` to compensate fractional collateral. */ event AuctionNFTSettle( address indexed borrower, uint256 collateral, uint256 lp, uint256 index ); /** * @notice Emitted when a `Claimaible Reserve Auction` is started. * @param claimableReservesRemaining Amount of claimable reserves which has not yet been taken (`WAD` precision). * @param auctionPrice Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @param currentBurnEpoch Current burn epoch. */ event KickReserveAuction( uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch ); /** * @notice Emitted when a `Claimaible Reserve Auction` is taken. * @param claimableReservesRemaining Amount of claimable reserves which has not yet been taken (`WAD` precision). * @param auctionPrice Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @param currentBurnEpoch Current burn epoch. */ event ReserveAuction( uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch ); /**************************/ /*** LP transfer events ***/ /**************************/ /** * @notice Emitted when owner increase the `LP` allowance of a spender at specified indexes with specified amounts. * @param owner `LP` owner. * @param spender Address approved to transfer `LP`. * @param indexes Bucket indexes of `LP` approved. * @param amounts `LP` amounts added (ordered by indexes, `WAD` precision). */ event IncreaseLPAllowance( address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts ); /** * @notice Emitted when owner decrease the `LP` allowance of a spender at specified indexes with specified amounts. * @param owner `LP` owner. * @param spender Address approved to transfer `LP`. * @param indexes Bucket indexes of `LP` approved. * @param amounts `LP` amounts removed (ordered by indexes, `WAD` precision). */ event DecreaseLPAllowance( address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts ); /** * @notice Emitted when lender removes the allowance of a spender for their `LP`. * @param owner `LP` owner. * @param spender Address that is having it's allowance revoked. * @param indexes List of bucket index to remove the allowance from. */ event RevokeLPAllowance( address indexed owner, address indexed spender, uint256[] indexes ); /** * @notice Emitted when lender whitelists addresses to accept `LP` from. * @param lender Recipient that approves new owner for `LP`. * @param transferors List of addresses that can transfer `LP` to lender. */ event ApproveLPTransferors( address indexed lender, address[] transferors ); /** * @notice Emitted when lender removes addresses from the `LP` transferors whitelist. * @param lender Recipient that approves new owner for `LP`. * @param transferors List of addresses that won't be able to transfer `LP` to lender anymore. */ event RevokeLPTransferors( address indexed lender, address[] transferors ); /** * @notice Emitted when a lender transfers their `LP` to a different address. * @dev Used by `PositionManager.memorializePositions()`. * @param owner The original owner address of the position. * @param newOwner The new owner address of the position. * @param indexes Array of price bucket indexes at which `LP` were transferred. * @param lp Amount of `LP` transferred (`WAD` precision). */ event TransferLP( address owner, address newOwner, uint256[] indexes, uint256 lp ); /**************************/ /*** Pool common events ***/ /**************************/ /** * @notice Emitted when `LP` are forfeited as a result of the bucket losing all assets. * @param index The index of the bucket. * @param lpForfeited Amount of `LP` forfeited by lenders (`WAD` precision). */ event BucketBankruptcy( uint256 indexed index, uint256 lpForfeited ); /** * @notice Emitted when a flashloan is taken from pool. * @param receiver The address receiving the flashloan. * @param token The address of token flashloaned from pool. * @param amount The amount of tokens flashloaned from pool (token precision). */ event Flashloan( address indexed receiver, address indexed token, uint256 amount ); /** * @notice Emitted when a loan `Np to Tp ratio` is restamped. * @param borrower Identifies the loan to update the `Np to Tp ratio`. */ event LoanStamped( address indexed borrower ); /** * @notice Emitted when pool interest rate is reset. This happens when `interest rate > 10%` and `debtEma < 5%` of `depositEma` * @param oldRate Old pool interest rate. * @param newRate New pool interest rate. */ event ResetInterestRate( uint256 oldRate, uint256 newRate ); /** * @notice Emitted when pool interest rate is updated. * @param oldRate Old pool interest rate. * @param newRate New pool interest rate. */ event UpdateInterestRate( uint256 oldRate, uint256 newRate ); /** * @notice Emitted when interest accural or update interest overflows. */ event InterestUpdateFailure(); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; /** * @title Pool Errors. */ interface IPoolErrors { /**************************/ /*** Common Pool Errors ***/ /**************************/ /** * @notice Adding liquidity above current auction price. */ error AddAboveAuctionPrice(); /** * @notice The action cannot be executed on an active auction. */ error AuctionActive(); /** * @notice Attempted auction to clear doesn't meet conditions. */ error AuctionNotClearable(); /** * @notice Auction does not meet requirements to take liquidity. */ error AuctionNotTakeable(); /** * @notice Head auction should be cleared prior of executing this action. */ error AuctionNotCleared(); /** * @notice The auction price is greater than the arbed bucket price. */ error AuctionPriceGtBucketPrice(); /** * @notice Pool already initialized. */ error AlreadyInitialized(); /** * @notice Borrower is attempting to create or modify a loan such that their loan's quote token would be less than the pool's minimum debt amount. */ error AmountLTMinDebt(); /** * @notice Recipient of borrowed quote tokens doesn't match the caller of the `drawDebt` function. */ error BorrowerNotSender(); /** * @notice Borrower has a healthy over-collateralized position. */ error BorrowerOk(); /** * @notice Borrower is attempting to borrow more quote token than they have collateral for. */ error BorrowerUnderCollateralized(); /** * @notice Operation cannot be executed in the same block when bucket becomes insolvent. */ error BucketBankruptcyBlock(); /** * @notice User attempted to merge collateral from a lower price bucket into a higher price bucket. */ error CannotMergeToHigherPrice(); /** * @notice User attempted an operation which does not exceed the dust amount, or leaves behind less than the dust amount. */ error DustAmountNotExceeded(); /** * @notice Callback invoked by `flashLoan` function did not return the expected hash (see `ERC-3156` spec). */ error FlashloanCallbackFailed(); /** * @notice Balance of pool contract before flashloan is different than the balance after flashloan. */ error FlashloanIncorrectBalance(); /** * @notice Pool cannot facilitate a flashloan for the specified token address. */ error FlashloanUnavailableForToken(); /** * @notice User is attempting to move or pull more collateral than is available. */ error InsufficientCollateral(); /** * @notice Lender is attempting to move or remove more collateral they have claim to in the bucket. * @notice Lender is attempting to remove more collateral they have claim to in the bucket. * @notice Lender must have enough `LP` to claim the desired amount of quote from the bucket. */ error InsufficientLP(); /** * @notice Bucket must have more quote available in the bucket than the lender is attempting to claim. */ error InsufficientLiquidity(); /** * @notice When increasing / decreasing `LP` allowances indexes and amounts arrays parameters should have same length. */ error InvalidAllowancesInput(); /** * @notice When transferring `LP` between indices, the new index must be a valid index. */ error InvalidIndex(); /** * @notice The amount used for performed action should be greater than `0`. */ error InvalidAmount(); /** * @notice Borrower is attempting to borrow more quote token than is available before the supplied `limitIndex`. */ error LimitIndexExceeded(); /** * @notice When moving quote token `HTP` must stay below `LUP`. * @notice When removing quote token `HTP` must stay below `LUP`. */ error LUPBelowHTP(); /** * @notice From index and to index arguments to move are the same. */ error MoveToSameIndex(); /** * @notice Owner of the `LP` must have approved the new owner prior to transfer. */ error NoAllowance(); /** * @notice Actor is attempting to take or clear an inactive auction. */ error NoAuction(); /** * @notice No pool reserves are claimable. */ error NoReserves(); /** * @notice Actor is attempting to take or clear an inactive reserves auction. */ error NoReservesAuction(); /** * @notice Lender must have non-zero `LP` when attemptign to remove quote token from the pool. */ error NoClaim(); /** * @notice Borrower has no debt to liquidate. * @notice Borrower is attempting to repay when they have no outstanding debt. */ error NoDebt(); /** * @notice Actor is attempting to kick with bucket price below the `LUP`. */ error PriceBelowLUP(); /** * @notice Lender is attempting to remove quote tokens from a bucket that exists above active auction debt from top-of-book downward. */ error RemoveDepositLockedByAuctionDebt(); /** * @notice User attempted to kick off a new auction less than `2` weeks since the last auction completed. */ error ReserveAuctionTooSoon(); /** * @notice Current block timestamp has reached or exceeded a user-provided expiration. */ error TransactionExpired(); /** * @notice The address that transfer `LP` is not approved by the `LP` receiving address. */ error TransferorNotApproved(); /** * @notice Owner of the `LP` attemps to transfer `LP` to same address. */ error TransferToSameOwner(); /** * @notice The DebtToCollateral of the loan to be inserted in loans heap is zero. */ error ZeroDebtToCollateral(); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; interface IERC3156FlashBorrower { /** * @dev Receive a flash loan. * @param initiator The initiator of the loan. * @param token The loan currency. * @param amount The amount of tokens lent (token precision). * @param fee The additional amount of tokens to repay. * @param data Arbitrary data structure, intended to contain user-defined parameters. * @return The `keccak256` hash of `ERC3156FlashBorrower.onFlashLoan` */ function onFlashLoan( address initiator, address token, uint256 amount, uint256 fee, bytes calldata data ) external returns (bytes32); }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { Clone } from '@clones/Clone.sol'; import { ReentrancyGuard } from '@openzeppelin/contracts/security/ReentrancyGuard.sol'; import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol'; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IPool, IPoolImmutables, IPoolBorrowerActions, IPoolLPActions, IPoolLenderActions, IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions, IPoolState, IPoolDerivedState, IERC20Token } from '../interfaces/pool/IPool.sol'; import { PoolState, AuctionsState, DepositsState, Loan, LoansState, InflatorState, EmaState, InterestState, PoolBalancesState, ReserveAuctionState, Bucket, Lender, Borrower, Kicker, BurnEvent, Liquidation } from '../interfaces/pool/commons/IPoolState.sol'; import { KickResult, SettleResult, TakeResult, RemoveQuoteParams, MoveQuoteParams, AddQuoteParams, KickReserveAuctionParams } from '../interfaces/pool/commons/IPoolInternals.sol'; import { COLLATERALIZATION_FACTOR, _determineInflatorState, _priceAt, _roundToScale } from '../libraries/helpers/PoolHelper.sol'; import { _revertIfAuctionDebtLocked, _revertIfAuctionClearable, _revertAfterExpiry, _revertIfAuctionPriceBelow, _revertIfActiveAuctions } from '../libraries/helpers/RevertsHelper.sol'; import { Buckets } from '../libraries/internal/Buckets.sol'; import { Deposits } from '../libraries/internal/Deposits.sol'; import { Loans } from '../libraries/internal/Loans.sol'; import { Maths } from '../libraries/internal/Maths.sol'; import { BorrowerActions } from '../libraries/external/BorrowerActions.sol'; import { LenderActions } from '../libraries/external/LenderActions.sol'; import { LPActions } from '../libraries/external/LPActions.sol'; import { KickerActions } from '../libraries/external/KickerActions.sol'; import { TakerActions } from '../libraries/external/TakerActions.sol'; import { PoolCommons } from '../libraries/external/PoolCommons.sol'; /** * @title Pool Contract * @dev Base contract and entrypoint for commong logic of both `ERC20` and `ERC721` pools. */ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { using SafeERC20 for IERC20; /*****************/ /*** Constants ***/ /*****************/ /// @dev Immutable pool type arg offset. uint256 internal constant POOL_TYPE = 0; /// @dev Immutable `Ajna` token address arg offset. uint256 internal constant AJNA_ADDRESS = 1; /// @dev Immutable collateral token address arg offset. uint256 internal constant COLLATERAL_ADDRESS = 21; /// @dev Immutable quote token address arg offset. uint256 internal constant QUOTE_ADDRESS = 41; /// @dev Immutable quote token scale arg offset. uint256 internal constant QUOTE_SCALE = 61; /***********************/ /*** State Variables ***/ /***********************/ AuctionsState internal auctions; DepositsState internal deposits; LoansState internal loans; InflatorState internal inflatorState; EmaState internal emaState; InterestState internal interestState; PoolBalancesState internal poolBalances; ReserveAuctionState internal reserveAuction; /// @dev deposit index -> bucket mapping mapping(uint256 => Bucket) internal buckets; bool internal isPoolInitialized; /// @dev owner address -> new owner address -> deposit index -> allowed amount mapping mapping(address => mapping(address => mapping(uint256 => uint256))) private _lpAllowances; /// @dev owner address -> transferor address -> approved flag mapping mapping(address => mapping(address => bool)) public override approvedTransferors; /******************/ /*** Immutables ***/ /******************/ /// @inheritdoc IPoolImmutables function poolType() external pure override returns (uint8) { return _getArgUint8(POOL_TYPE); } /// @inheritdoc IPoolImmutables function collateralAddress() external pure override returns (address) { return _getArgAddress(COLLATERAL_ADDRESS); } /// @inheritdoc IPoolImmutables function quoteTokenAddress() external pure override returns (address) { return _getArgAddress(QUOTE_ADDRESS); } /// @inheritdoc IPoolImmutables function quoteTokenScale() external pure override returns (uint256) { return _getArgUint256(QUOTE_SCALE); } /*********************************/ /*** Lender External Functions ***/ /*********************************/ /// @inheritdoc IPoolLenderActions function addQuoteToken( uint256 amount_, uint256 index_, uint256 expiry_ ) external override nonReentrant returns (uint256 bucketLP_, uint256 addedAmount_) { _revertAfterExpiry(expiry_); _revertIfAuctionClearable(auctions, loans); _revertIfAuctionPriceBelow(auctions, index_); PoolState memory poolState = _accruePoolInterest(); // round to token precision amount_ = _roundToScale(amount_, poolState.quoteTokenScale); uint256 newLup; (bucketLP_, addedAmount_, newLup) = LenderActions.addQuoteToken( buckets, deposits, poolState, AddQuoteParams({ amount: amount_, index: index_ }) ); // update pool interest rate state _updateInterestState(poolState, newLup); // move quote token amount from lender to pool _transferQuoteTokenFrom(msg.sender, amount_); } /// @inheritdoc IPoolLenderActions function moveQuoteToken( uint256 maxAmount_, uint256 fromIndex_, uint256 toIndex_, uint256 expiry_ ) external override nonReentrant returns (uint256 fromBucketLP_, uint256 toBucketLP_, uint256 movedAmount_) { _revertAfterExpiry(expiry_); _revertIfAuctionClearable(auctions, loans); _revertIfAuctionPriceBelow(auctions, toIndex_); PoolState memory poolState = _accruePoolInterest(); _revertIfAuctionDebtLocked(deposits, poolState.t0DebtInAuction, fromIndex_, poolState.inflator); MoveQuoteParams memory moveParams; moveParams.maxAmountToMove = maxAmount_; moveParams.fromIndex = fromIndex_; moveParams.toIndex = toIndex_; moveParams.maxT0DebtToCollateral = Loans.getMax(loans).t0DebtToCollateral; uint256 newLup; ( fromBucketLP_, toBucketLP_, movedAmount_, newLup ) = LenderActions.moveQuoteToken( buckets, deposits, poolState, moveParams ); // update pool interest rate state _updateInterestState(poolState, newLup); } /// @inheritdoc IPoolLenderActions function removeQuoteToken( uint256 maxAmount_, uint256 index_ ) external override nonReentrant returns (uint256 removedAmount_, uint256 redeemedLP_) { _revertIfAuctionClearable(auctions, loans); PoolState memory poolState = _accruePoolInterest(); _revertIfAuctionDebtLocked(deposits, poolState.t0DebtInAuction, index_, poolState.inflator); uint256 newLup; ( removedAmount_, redeemedLP_, newLup ) = LenderActions.removeQuoteToken( buckets, deposits, poolState, RemoveQuoteParams({ maxAmount: Maths.min(maxAmount_, _availableQuoteToken()), index: index_, maxT0DebtToCollateral: Loans.getMax(loans).t0DebtToCollateral }) ); // update pool interest rate state _updateInterestState(poolState, newLup); // move quote token amount from pool to lender _transferQuoteToken(msg.sender, removedAmount_); } /// @inheritdoc IPoolLenderActions function updateInterest() external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); _updateInterestState(poolState, Deposits.getLup(deposits, poolState.debt)); } /***********************************/ /*** Borrower External Functions ***/ /***********************************/ /// @inheritdoc IPoolBorrowerActions function stampLoan() external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); uint256 newLup = BorrowerActions.stampLoan( auctions, deposits, loans, poolState ); _updateInterestState(poolState, newLup); } /*****************************/ /*** Liquidation Functions ***/ /*****************************/ /** * @inheritdoc IPoolKickerActions * @dev === Write state === * @dev increment `poolBalances.t0DebtInAuction` and `poolBalances.t0Debt` accumulators * @dev update `t0Debt2ToCollateral` ratio, debt and collateral post action are considered 0 */ function kick( address borrower_, uint256 npLimitIndex_ ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); // kick auction KickResult memory result = KickerActions.kick( auctions, deposits, loans, poolState, borrower_, npLimitIndex_ ); // update in memory pool state struct poolState.t0DebtInAuction += result.t0KickedDebt; // adjust t0Debt2ToCollateral ratio _updateT0Debt2ToCollateral( result.t0KickedDebt, 0, // debt post kick (for loan in auction) not taken into account result.collateralPreAction, 0 // collateral post kick (for loan in auction) not taken into account ); // update pool balances state poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; // update pool interest rate state _updateInterestState(poolState, result.lup); if (result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); } /** * @inheritdoc IPoolKickerActions * @dev === Write state === */ function lenderKick( uint256 index_, uint256 npLimitIndex_ ) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); // kick auctions KickResult memory result = KickerActions.lenderKick( auctions, deposits, buckets, loans, poolState, index_, npLimitIndex_ ); // update in memory pool state struct poolState.t0DebtInAuction += result.t0KickedDebt; // adjust t0Debt2ToCollateral ratio _updateT0Debt2ToCollateral( result.t0KickedDebt, 0, // debt post kick (for loan in auction) not taken into account result.collateralPreAction, 0 // collateral post kick (for loan in auction) not taken into account ); // update pool balances state poolBalances.t0DebtInAuction = poolState.t0DebtInAuction; // update pool interest rate state _updateInterestState(poolState, result.lup); // transfer from kicker to pool the difference to cover bond if (result.amountToCoverBond != 0) _transferQuoteTokenFrom(msg.sender, result.amountToCoverBond); } /** * @inheritdoc IPoolKickerActions * @dev === Write state === * @dev decrease kicker's `claimable` accumulator * @dev decrease auctions `totalBondEscrowed` accumulator */ function withdrawBonds( address recipient_, uint256 maxAmount_ ) external override nonReentrant returns (uint256 withdrawnAmount_) { withdrawnAmount_ = KickerActions.withdrawBonds(auctions, recipient_, maxAmount_); _transferQuoteToken(recipient_, withdrawnAmount_); } /*********************************/ /*** Reserve Auction Functions ***/ /*********************************/ /** * @inheritdoc IPoolKickerActions * @dev === Write state === * @dev increment `latestBurnEpoch` counter * @dev update `reserveAuction.latestBurnEventEpoch` and burn event `timestamp` state * @dev === Reverts on === * @dev no reserves to claim `NoReserves()` * @dev 5 days not passed `ReserveAuctionTooSoon()` * @dev unsettled liquidation `AuctionActive()` * @dev === Emit events === * @dev - `KickReserveAuction` */ function kickReserveAuction() external override nonReentrant { _revertIfActiveAuctions(auctions); // start a new claimable reserve auction, passing in relevant parameters such as the current pool size, debt, balance, and inflator value KickerActions.kickReserveAuction( auctions, reserveAuction, KickReserveAuctionParams({ poolSize: Deposits.treeSum(deposits), t0PoolDebt: poolBalances.t0Debt, poolBalance: _getNormalizedPoolQuoteTokenBalance(), inflator: inflatorState.inflator }) ); } /** * @inheritdoc IPoolTakerActions * @dev === Write state === * @dev increment `reserveAuction.totalAjnaBurned` accumulator * @dev update burn event `totalInterest` and `totalBurned` accumulators */ function takeReserves( uint256 maxAmount_ ) external override nonReentrant returns (uint256 amount_) { uint256 ajnaRequired; (amount_, ajnaRequired) = TakerActions.takeReserves( reserveAuction, maxAmount_, _getArgUint256(QUOTE_SCALE) ); // burn required number of ajna tokens to take quote from reserves IERC20(_getArgAddress(AJNA_ADDRESS)).safeTransferFrom(msg.sender, address(this), ajnaRequired); IERC20Token(_getArgAddress(AJNA_ADDRESS)).burn(ajnaRequired); // transfer quote token to caller _transferQuoteToken(msg.sender, amount_); } /*****************************/ /*** Transfer LP Functions ***/ /*****************************/ /// @inheritdoc IPoolLPActions function increaseLPAllowance( address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external override nonReentrant { LPActions.increaseLPAllowance( _lpAllowances[msg.sender][spender_], spender_, indexes_, amounts_ ); } /// @inheritdoc IPoolLPActions function decreaseLPAllowance( address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external override nonReentrant { LPActions.decreaseLPAllowance( _lpAllowances[msg.sender][spender_], spender_, indexes_, amounts_ ); } /// @inheritdoc IPoolLPActions function revokeLPAllowance( address spender_, uint256[] calldata indexes_ ) external override nonReentrant { LPActions.revokeLPAllowance( _lpAllowances[msg.sender][spender_], spender_, indexes_ ); } /// @inheritdoc IPoolLPActions function approveLPTransferors( address[] calldata transferors_ ) external override { LPActions.approveLPTransferors( approvedTransferors[msg.sender], transferors_ ); } /// @inheritdoc IPoolLPActions function revokeLPTransferors( address[] calldata transferors_ ) external override { LPActions.revokeLPTransferors( approvedTransferors[msg.sender], transferors_ ); } /// @inheritdoc IPoolLPActions function transferLP( address owner_, address newOwner_, uint256[] calldata indexes_ ) external override nonReentrant { LPActions.transferLP( buckets, _lpAllowances, approvedTransferors, owner_, newOwner_, indexes_ ); } /*****************************/ /*** Pool Helper Functions ***/ /*****************************/ /** * @notice Accrues pool interest in current block and returns pool details. * @dev external libraries call: `PoolCommons.accrueInterest` * @dev === Write state === * @dev - `PoolCommons.accrueInterest` - `Deposits.mult` (scale `Fenwick` tree with new interest accrued): * @dev update scaling array state * @dev - increment `reserveAuction.totalInterestEarned` accumulator * @return poolState_ Struct containing pool details. */ function _accruePoolInterest() internal returns (PoolState memory poolState_) { poolState_.t0Debt = poolBalances.t0Debt; poolState_.t0DebtInAuction = poolBalances.t0DebtInAuction; poolState_.collateral = poolBalances.pledgedCollateral; poolState_.inflator = inflatorState.inflator; poolState_.rate = interestState.interestRate; poolState_.poolType = _getArgUint8(POOL_TYPE); poolState_.quoteTokenScale = _getArgUint256(QUOTE_SCALE); // check if t0Debt is not equal to 0, indicating that there is debt to be tracked for the pool if (poolState_.t0Debt != 0) { // Calculate prior pool debt poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // calculate elapsed time since inflator was last updated uint256 elapsed = block.timestamp - inflatorState.inflatorUpdate; // set isNewInterestAccrued field to true if elapsed time is not 0, indicating that new interest may have accrued poolState_.isNewInterestAccrued = elapsed != 0; // if new interest may have accrued, call accrueInterest function and update inflator and debt fields of poolState_ struct if (poolState_.isNewInterestAccrued) { try PoolCommons.accrueInterest( emaState, deposits, poolState_, Loans.getMax(loans).t0DebtToCollateral, elapsed ) returns (uint256 newInflator, uint256 newInterest) { poolState_.inflator = newInflator; // After debt owed to lenders has accrued, calculate current debt owed by borrowers poolState_.debt = Maths.wmul(poolState_.t0Debt, poolState_.inflator); // update total interest earned accumulator with the newly accrued interest reserveAuction.totalInterestEarned += newInterest; } catch { poolState_.isNewInterestAccrued = false; emit InterestUpdateFailure(); } } } } /** * @notice Helper function to update pool state post take and bucket take actions. * @param result_ Struct containing details of take result. * @param poolState_ Struct containing pool details. */ function _updatePostTakeState( TakeResult memory result_, PoolState memory poolState_ ) internal { // update in memory pool state struct poolState_.debt = result_.poolDebt; poolState_.t0Debt = result_.t0PoolDebt; poolState_.t0DebtInAuction -= result_.t0DebtInAuctionChange; poolState_.collateral -= (result_.collateralAmount + result_.compensatedCollateral); // deduct collateral taken plus collateral compensated if NFT auction settled // adjust t0Debt2ToCollateral ratio if auction settled by take action if (result_.settledAuction) { _updateT0Debt2ToCollateral( 0, // debt pre take (for loan in auction) not taken into account result_.debtPostAction, 0, // collateral pre take (for loan in auction) not taken into account result_.collateralPostAction ); } // update pool balances state poolBalances.t0Debt = poolState_.t0Debt; poolBalances.t0DebtInAuction = poolState_.t0DebtInAuction; poolBalances.pledgedCollateral = poolState_.collateral; // update pool interest rate state _updateInterestState(poolState_, result_.newLup); } /** * @notice Helper function to update pool state post settle action. * @param result_ Struct containing details of settle result. * @param poolState_ Struct containing pool details. */ function _updatePostSettleState( SettleResult memory result_, PoolState memory poolState_ ) internal { // update in memory pool state struct poolState_.debt -= result_.debtSettled; poolState_.t0Debt -= result_.t0DebtSettled; poolState_.t0DebtInAuction -= result_.t0DebtSettled; poolState_.collateral -= result_.collateralSettled; // update pool balances state poolBalances.t0Debt = poolState_.t0Debt; poolBalances.t0DebtInAuction = poolState_.t0DebtInAuction; poolBalances.pledgedCollateral = poolState_.collateral; // update pool interest rate state _updateInterestState(poolState_, Deposits.getLup(deposits, poolState_.debt)); } /** * @notice Adjusts the `t0` debt 2 to collateral ratio, `interestState.t0Debt2ToCollateral`. * @dev Anytime a borrower's debt or collateral changes, the `interestState.t0Debt2ToCollateral` must be updated. * @dev === Write state === * @dev update `interestState.t0Debt2ToCollateral` accumulator * @param debtPreAction_ Borrower's debt before the action * @param debtPostAction_ Borrower's debt after the action * @param colPreAction_ Borrower's collateral before the action * @param colPostAction_ Borrower's collateral after the action */ function _updateT0Debt2ToCollateral( uint256 debtPreAction_, uint256 debtPostAction_, uint256 colPreAction_, uint256 colPostAction_ ) internal { uint256 debt2ColAccumPreAction = colPreAction_ != 0 ? debtPreAction_ ** 2 / colPreAction_ : 0; uint256 debt2ColAccumPostAction = colPostAction_ != 0 ? debtPostAction_ ** 2 / colPostAction_ : 0; if (debt2ColAccumPreAction != 0 || debt2ColAccumPostAction != 0) { uint256 curT0Debt2ToCollateral = interestState.t0Debt2ToCollateral; curT0Debt2ToCollateral += debt2ColAccumPostAction; curT0Debt2ToCollateral -= debt2ColAccumPreAction; interestState.t0Debt2ToCollateral = curT0Debt2ToCollateral; } } /** * @notice Update interest rate and inflator of the pool. * @dev external libraries call: `PoolCommons.updateInterestState` * @dev === Write state === * @dev - `PoolCommons.updateInterestState` * @dev `EMA`s accumulators * @dev interest rate accumulator and `interestRateUpdate` state * @dev pool inflator and `inflatorUpdate` state * @dev === Emit events === * @dev `PoolCommons.updateInterestState`: `UpdateInterestRate` * @param poolState_ Struct containing pool details. * @param lup_ Current `LUP` in pool. */ function _updateInterestState( PoolState memory poolState_, uint256 lup_ ) internal { try PoolCommons.updateInterestState(interestState, emaState, deposits, poolState_, lup_) {} catch { emit InterestUpdateFailure(); } (uint208 newInflator, bool updateTimestamp) = _determineInflatorState(poolState_, inflatorState); inflatorState.inflator = newInflator; if (updateTimestamp) inflatorState.inflatorUpdate = uint48(block.timestamp); } /** * @notice Helper function to transfer amount of quote tokens from sender to pool contract. * @param from_ Sender address. * @param amount_ Amount to transfer from sender (`WAD` precision). Scaled to quote token precision before transfer. */ function _transferQuoteTokenFrom(address from_, uint256 amount_) internal { // Transfer amount in favour of the pool uint256 transferAmount = Maths.ceilDiv(amount_, _getArgUint256(QUOTE_SCALE)); IERC20(_getArgAddress(QUOTE_ADDRESS)).safeTransferFrom(from_, address(this), transferAmount); } /** * @notice Helper function to transfer amount of quote tokens from pool contract. * @param to_ Receiver address. * @param amount_ Amount to transfer to receiver (`WAD` precision). Scaled to quote token precision before transfer. */ function _transferQuoteToken(address to_, uint256 amount_) internal { IERC20(_getArgAddress(QUOTE_ADDRESS)).safeTransfer(to_, amount_ / _getArgUint256(QUOTE_SCALE)); } /** * @notice Returns the quote token amount available to take loans or to be removed from pool. * Ensures claimable reserves and auction bonds are not used when taking loans. */ function _availableQuoteToken() internal view returns (uint256 quoteAvailable_) { uint256 poolBalance = _getNormalizedPoolQuoteTokenBalance(); uint256 escrowedAmounts = auctions.totalBondEscrowed + reserveAuction.unclaimed; if (poolBalance > escrowedAmounts) quoteAvailable_ = poolBalance - escrowedAmounts; } /** * @notice Returns the pool quote token balance normalized to `WAD` to be used for calculating pool reserves. */ function _getNormalizedPoolQuoteTokenBalance() internal view returns (uint256) { return IERC20(_getArgAddress(QUOTE_ADDRESS)).balanceOf(address(this)) * _getArgUint256(QUOTE_SCALE); } /*******************************/ /*** External View Functions ***/ /*******************************/ /// @inheritdoc IPoolState function auctionInfo( address borrower_ ) external view override returns ( address kicker_, uint256 bondFactor_, uint256 bondSize_, uint256 kickTime_, uint256 referencePrice_, uint256 neutralPrice_, uint256 debtToCollateral_, address head_, address next_, address prev_ ) { Liquidation storage liquidation = auctions.liquidations[borrower_]; return ( liquidation.kicker, liquidation.bondFactor, liquidation.bondSize, liquidation.kickTime, liquidation.referencePrice, liquidation.neutralPrice, liquidation.debtToCollateral, auctions.head, liquidation.next, liquidation.prev ); } /// @inheritdoc IPoolState function borrowerInfo( address borrower_ ) external view override returns (uint256, uint256, uint256) { Borrower storage borrower = loans.borrowers[borrower_]; return ( borrower.t0Debt, borrower.collateral, borrower.npTpRatio ); } /// @inheritdoc IPoolState function bucketInfo( uint256 index_ ) external view override returns (uint256, uint256, uint256, uint256, uint256) { uint256 scale = Deposits.scale(deposits, index_); Bucket storage bucket = buckets[index_]; return ( bucket.lps, bucket.collateral, bucket.bankruptcyTime, Maths.wmul(scale, Deposits.unscaledValueAt(deposits, index_)), scale ); } /// @inheritdoc IPoolDerivedState function bucketExchangeRate( uint256 index_ ) external view returns (uint256 exchangeRate_) { Bucket storage bucket = buckets[index_]; exchangeRate_ = Buckets.getExchangeRate( bucket.collateral, bucket.lps, Deposits.valueAt(deposits, index_), _priceAt(index_) ); } /// @inheritdoc IPoolState function currentBurnEpoch() external view returns (uint256) { return reserveAuction.latestBurnEventEpoch; } /// @inheritdoc IPoolState function burnInfo(uint256 burnEventEpoch_) external view returns (uint256, uint256, uint256) { BurnEvent storage burnEvent = reserveAuction.burnEvents[burnEventEpoch_]; return ( burnEvent.timestamp, burnEvent.totalInterest, burnEvent.totalBurned ); } /// @inheritdoc IPoolState function debtInfo() external view returns (uint256, uint256, uint256, uint256) { return PoolCommons.debtInfo(poolBalances, inflatorState, interestState); } /// @inheritdoc IPoolDerivedState function depositUpToIndex(uint256 index_) external view override returns (uint256) { return Deposits.prefixSum(deposits, index_); } /// @inheritdoc IPoolDerivedState function depositIndex(uint256 debt_) external view override returns (uint256) { return Deposits.findIndexOfSum(deposits, debt_); } /// @inheritdoc IPoolDerivedState function depositSize() external view override returns (uint256) { return Deposits.treeSum(deposits); } /// @inheritdoc IPoolDerivedState function depositUtilization() external view override returns (uint256) { return PoolCommons.utilization(emaState); } /// @inheritdoc IPoolDerivedState function depositScale(uint256 index_) external view override returns (uint256) { return deposits.scaling[index_]; } /// @inheritdoc IPoolState function emasInfo() external view override returns (uint256, uint256, uint256, uint256) { return ( emaState.debtColEma, emaState.lupt0DebtEma, emaState.debtEma, emaState.depositEma ); } /// @inheritdoc IPoolState function inflatorInfo() external view override returns (uint256, uint256) { return ( inflatorState.inflator, inflatorState.inflatorUpdate ); } /// @inheritdoc IPoolState function interestRateInfo() external view returns (uint256, uint256) { return ( interestState.interestRate, interestState.interestRateUpdate ); } /// @inheritdoc IPoolState function kickerInfo( address kicker_ ) external view override returns (uint256, uint256) { Kicker storage kicker = auctions.kickers[kicker_]; return( kicker.claimable, kicker.locked ); } /// @inheritdoc IPoolState function lenderInfo( uint256 index_, address lender_ ) external view override returns (uint256 lpBalance_, uint256 depositTime_) { Bucket storage bucket = buckets[index_]; Lender storage lender = bucket.lenders[lender_]; depositTime_ = lender.depositTime; if (bucket.bankruptcyTime < depositTime_) lpBalance_ = lender.lps; } /// @inheritdoc IPoolState function lpAllowance( uint256 index_, address spender_, address owner_ ) external view override returns (uint256 allowance_) { allowance_ = _lpAllowances[owner_][spender_][index_]; } /// @inheritdoc IPoolState function loanInfo( uint256 loanId_ ) external view override returns (address, uint256) { Loan memory loan = Loans.getByIndex(loans, loanId_); return ( loan.borrower, loan.t0DebtToCollateral ); } /// @inheritdoc IPoolState function loansInfo() external view override returns (address, uint256, uint256) { Loan memory maxLoan = Loans.getMax(loans); return ( maxLoan.borrower, maxLoan.t0DebtToCollateral, Loans.noOfLoans(loans) ); } /// @inheritdoc IPoolState function pledgedCollateral() external view override returns (uint256) { return poolBalances.pledgedCollateral; } /// @inheritdoc IPoolState function reservesInfo() external view override returns (uint256, uint256, uint256, uint256, uint256) { return ( auctions.totalBondEscrowed, reserveAuction.unclaimed, reserveAuction.kicked, reserveAuction.lastKickedReserves, reserveAuction.totalInterestEarned ); } /// @inheritdoc IPoolState function totalAuctionsInPool() external view override returns (uint256) { return auctions.noOfAuctions; } /// @inheritdoc IPoolState function totalT0Debt() external view override returns (uint256) { return poolBalances.t0Debt; } /// @inheritdoc IPoolState function totalT0DebtInAuction() external view override returns (uint256) { return poolBalances.t0DebtInAuction; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { PRBMathSD59x18 } from "@prb-math/contracts/PRBMathSD59x18.sol"; import { PRBMathUD60x18 } from "@prb-math/contracts/PRBMathUD60x18.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { DepositsState, EmaState, InflatorState, InterestState, PoolBalancesState, PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; import { IERC3156FlashBorrower } from '../../interfaces/pool/IERC3156FlashBorrower.sol'; import { _dwatp, _htp, _indexOf, MAX_FENWICK_INDEX, MIN_PRICE, MAX_PRICE } from '../helpers/PoolHelper.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Loans } from '../internal/Loans.sol'; import { Maths } from '../internal/Maths.sol'; /** @title PoolCommons library @notice External library containing logic for common pool functionality: - interest rate accrual and interest rate params update - pool utilization */ library PoolCommons { using SafeERC20 for IERC20; /*****************/ /*** Constants ***/ /*****************/ uint256 internal constant CUBIC_ROOT_1000000 = 100 * 1e18; uint256 internal constant ONE_THIRD = 0.333333333333333334 * 1e18; uint256 internal constant INCREASE_COEFFICIENT = 1.1 * 1e18; uint256 internal constant DECREASE_COEFFICIENT = 0.9 * 1e18; int256 internal constant PERCENT_102 = 1.02 * 1e18; int256 internal constant NEG_H_MAU_HOURS = -0.057762265046662105 * 1e18; // -ln(2)/12 int256 internal constant NEG_H_TU_HOURS = -0.008251752149523158 * 1e18; // -ln(2)/84 /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event Flashloan(address indexed receiver, address indexed token, uint256 amount); event ResetInterestRate(uint256 oldRate, uint256 newRate); event UpdateInterestRate(uint256 oldRate, uint256 newRate); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error FlashloanCallbackFailed(); error FlashloanIncorrectBalance(); /*************************/ /*** Local Var Structs ***/ /*************************/ /// @dev Struct used for `updateInterestState` function local vars. struct UpdateInterestLocalVars { uint256 debtEma; uint256 depositEma; uint256 debtColEma; uint256 lupt0DebtEma; uint256 t0Debt2ToCollateral; uint256 newMeaningfulDeposit; uint256 newDebt; uint256 newDebtCol; uint256 newLupt0Debt; uint256 lastEmaUpdate; int256 elapsed; int256 weightMau; int256 weightTu; uint256 newInterestRate; uint256 nonAuctionedT0Debt; } /**************************/ /*** External Functions ***/ /**************************/ /** * @notice Calculates EMAs, caches values required for calculating interest rate, and saves new values in storage. * @notice Calculates new pool interest rate (Never called more than once every 12 hours) and saves new values in storage. * @dev === Write state === * @dev `EMA`s state * @dev interest rate accumulator and `interestRateUpdate` state * @dev === Emit events === * @dev - `UpdateInterestRate` / `ResetInterestRate` */ function updateInterestState( InterestState storage interestParams_, EmaState storage emaParams_, DepositsState storage deposits_, PoolState memory poolState_, uint256 lup_ ) external { UpdateInterestLocalVars memory vars; // load existing EMA values vars.debtEma = emaParams_.debtEma; vars.depositEma = emaParams_.depositEma; vars.debtColEma = emaParams_.debtColEma; vars.lupt0DebtEma = emaParams_.lupt0DebtEma; vars.lastEmaUpdate = emaParams_.emaUpdate; vars.t0Debt2ToCollateral = interestParams_.t0Debt2ToCollateral; // calculate new interest params vars.nonAuctionedT0Debt = poolState_.t0Debt - poolState_.t0DebtInAuction; vars.newDebt = Maths.wmul(vars.nonAuctionedT0Debt, poolState_.inflator); // new meaningful deposit cannot be less than pool's debt vars.newMeaningfulDeposit = Maths.max( _meaningfulDeposit( deposits_, poolState_.t0DebtInAuction, vars.nonAuctionedT0Debt, poolState_.inflator, vars.t0Debt2ToCollateral ), vars.newDebt ); vars.newDebtCol = Maths.wmul(poolState_.inflator, vars.t0Debt2ToCollateral); vars.newLupt0Debt = Maths.wmul(lup_, vars.nonAuctionedT0Debt); // update EMAs only once per block if (vars.lastEmaUpdate != block.timestamp) { // first time EMAs are updated, initialize EMAs if (vars.lastEmaUpdate == 0) { vars.debtEma = vars.newDebt; vars.depositEma = vars.newMeaningfulDeposit; vars.debtColEma = vars.newDebtCol; vars.lupt0DebtEma = vars.newLupt0Debt; } else { vars.elapsed = int256(Maths.wdiv(block.timestamp - vars.lastEmaUpdate, 1 hours)); vars.weightMau = PRBMathSD59x18.exp(PRBMathSD59x18.mul(NEG_H_MAU_HOURS, vars.elapsed)); vars.weightTu = PRBMathSD59x18.exp(PRBMathSD59x18.mul(NEG_H_TU_HOURS, vars.elapsed)); // calculate the t0 debt EMA, used for MAU vars.debtEma = uint256( PRBMathSD59x18.mul(vars.weightMau, int256(vars.debtEma)) + PRBMathSD59x18.mul(1e18 - vars.weightMau, int256(interestParams_.debt)) ); // update the meaningful deposit EMA, used for MAU vars.depositEma = uint256( PRBMathSD59x18.mul(vars.weightMau, int256(vars.depositEma)) + PRBMathSD59x18.mul(1e18 - vars.weightMau, int256(interestParams_.meaningfulDeposit)) ); // calculate the debt squared to collateral EMA, used for TU vars.debtColEma = uint256( PRBMathSD59x18.mul(vars.weightTu, int256(vars.debtColEma)) + PRBMathSD59x18.mul(1e18 - vars.weightTu, int256(interestParams_.debtCol)) ); // calculate the EMA of LUP * t0 debt vars.lupt0DebtEma = uint256( PRBMathSD59x18.mul(vars.weightTu, int256(vars.lupt0DebtEma)) + PRBMathSD59x18.mul(1e18 - vars.weightTu, int256(interestParams_.lupt0Debt)) ); } // save EMAs in storage emaParams_.debtEma = vars.debtEma; emaParams_.depositEma = vars.depositEma; emaParams_.debtColEma = vars.debtColEma; emaParams_.lupt0DebtEma = vars.lupt0DebtEma; // save last EMA update time emaParams_.emaUpdate = block.timestamp; } // reset interest rate if pool rate > 10% and debtEma < 5% of depositEma if ( poolState_.rate > 0.1 * 1e18 && vars.debtEma < Maths.wmul(vars.depositEma, 0.05 * 1e18) ) { interestParams_.interestRate = uint208(0.1 * 1e18); interestParams_.interestRateUpdate = uint48(block.timestamp); emit ResetInterestRate( poolState_.rate, 0.1 * 1e18 ); } // otherwise calculate and update interest rate if it has been more than 12 hours since the last update else if (block.timestamp - interestParams_.interestRateUpdate > 12 hours) { vars.newInterestRate = _calculateInterestRate( poolState_, vars.debtEma, vars.depositEma, vars.debtColEma, vars.lupt0DebtEma ); if (poolState_.rate != vars.newInterestRate) { interestParams_.interestRate = uint208(vars.newInterestRate); interestParams_.interestRateUpdate = uint48(block.timestamp); emit UpdateInterestRate( poolState_.rate, vars.newInterestRate ); } } // save new interest rate params to storage interestParams_.debt = vars.newDebt; interestParams_.meaningfulDeposit = vars.newMeaningfulDeposit; interestParams_.debtCol = vars.newDebtCol; interestParams_.lupt0Debt = vars.newLupt0Debt; } /** * @notice Calculates new pool interest and scale the fenwick tree to update amount of debt owed to lenders (saved in storage). * @dev === Write state === * @dev - `Deposits.mult` (scale `Fenwick` tree with new interest accrued): * @dev update `scaling` array state * @param emaParams_ Struct for pool `EMA`s state. * @param deposits_ Struct for pool deposits state. * @param poolState_ Current state of the pool. * @param maxT0DebtToCollateral_ Max t0 debt to collateral in Pool. * @param elapsed_ Time elapsed since last inflator update. * @return newInflator_ The new value of pool inflator. * @return newInterest_ The new interest accrued. */ function accrueInterest( EmaState storage emaParams_, DepositsState storage deposits_, PoolState calldata poolState_, uint256 maxT0DebtToCollateral_, uint256 elapsed_ ) external returns (uint256 newInflator_, uint256 newInterest_) { // Scale the borrower inflator to update amount of interest owed by borrowers uint256 pendingFactor = PRBMathUD60x18.exp((poolState_.rate * elapsed_) / 365 days); // calculate the highest threshold price newInflator_ = Maths.wmul(poolState_.inflator, pendingFactor); uint256 htp = _htp(maxT0DebtToCollateral_, poolState_.inflator); uint256 accrualIndex; if (htp > MAX_PRICE) accrualIndex = 1; // if HTP is over the highest price bucket then no buckets earn interest else if (htp < MIN_PRICE) accrualIndex = MAX_FENWICK_INDEX; // if HTP is under the lowest price bucket then all buckets earn interest else accrualIndex = _indexOf(htp); // else HTP bucket earn interest uint256 lupIndex = Deposits.findIndexOfSum(deposits_, poolState_.debt); // accrual price is less of lup and htp, and prices decrease as index increases if (lupIndex > accrualIndex) accrualIndex = lupIndex; uint256 interestEarningDeposit = Deposits.prefixSum(deposits_, accrualIndex); if (interestEarningDeposit != 0) { newInterest_ = Maths.wmul( _lenderInterestMargin(_utilization(emaParams_.debtEma, emaParams_.depositEma)), Maths.wmul(pendingFactor - Maths.WAD, poolState_.debt) ); // lender factor computation, capped at 10x the interest factor for borrowers uint256 lenderFactor = Maths.min( Maths.floorWdiv(newInterest_, interestEarningDeposit), Maths.wmul(pendingFactor - Maths.WAD, Maths.wad(10)) ) + Maths.WAD; // Scale the fenwick tree to update amount of debt owed to lenders Deposits.mult(deposits_, accrualIndex, lenderFactor); } } /** * @notice Executes a flashloan from current pool. * @dev === Reverts on === * @dev - `FlashloanCallbackFailed()` if receiver is not an `ERC3156FlashBorrower` * @dev - `FlashloanIncorrectBalance()` if pool balance after flashloan is different than initial balance * @param receiver_ Address of the contract which implements the appropriate interface to receive tokens. * @param token_ Address of the `ERC20` token caller wants to borrow. * @param amount_ The denormalized amount (dependent upon token precision) of tokens to borrow. * @param data_ User-defined calldata passed to the receiver. */ function flashLoan( IERC3156FlashBorrower receiver_, address token_, uint256 amount_, bytes calldata data_ ) external { IERC20 tokenContract = IERC20(token_); uint256 initialBalance = tokenContract.balanceOf(address(this)); tokenContract.safeTransfer( address(receiver_), amount_ ); if (receiver_.onFlashLoan(msg.sender, token_, amount_, 0, data_) != keccak256("ERC3156FlashBorrower.onFlashLoan")) revert FlashloanCallbackFailed(); tokenContract.safeTransferFrom( address(receiver_), address(this), amount_ ); if (tokenContract.balanceOf(address(this)) != initialBalance) revert FlashloanIncorrectBalance(); emit Flashloan(address(receiver_), token_, amount_); } /**************************/ /*** Internal Functions ***/ /**************************/ /** * @notice Calculates new pool interest rate. */ function _calculateInterestRate( PoolState memory poolState_, uint256 debtEma_, uint256 depositEma_, uint256 debtColEma_, uint256 lupt0DebtEma_ ) internal pure returns (uint256 newInterestRate_) { // meaningful actual utilization int256 mau; // meaningful actual utilization * 1.02 int256 mau102; if (poolState_.debt != 0) { // calculate meaningful actual utilization for interest rate update mau = int256(_utilization(debtEma_, depositEma_)); mau102 = (mau * PERCENT_102) / 1e18; } // calculate target utilization int256 tu = (lupt0DebtEma_ != 0) ? int256(Maths.wdiv(debtColEma_, lupt0DebtEma_)) : int(Maths.WAD); newInterestRate_ = poolState_.rate; // raise rates if 4*(tu-1.02*mau) < (tu+1.02*mau-1)^2-1 if (4 * (tu - mau102) < (((tu + mau102 - 1e18) / 1e9) ** 2) - 1e18) { newInterestRate_ = Maths.wmul(poolState_.rate, INCREASE_COEFFICIENT); // decrease rates if 4*(tu-mau) > 1-(tu+mau-1)^2 } else if (4 * (tu - mau) > 1e18 - ((tu + mau - 1e18) / 1e9) ** 2) { newInterestRate_ = Maths.wmul(poolState_.rate, DECREASE_COEFFICIENT); } // bound rates between 10 bps and 400% newInterestRate_ = Maths.min(4 * 1e18, Maths.max(0.001 * 1e18, newInterestRate_)); } /** * @notice Calculates pool meaningful actual utilization. * @param debtEma_ `EMA` of pool debt. * @param depositEma_ `EMA` of meaningful pool deposit. * @return utilization_ Pool meaningful actual utilization value. */ function _utilization( uint256 debtEma_, uint256 depositEma_ ) internal pure returns (uint256 utilization_) { if (depositEma_ != 0) utilization_ = Maths.wdiv(debtEma_, depositEma_); } /** * @notice Calculates lender interest margin. * @param mau_ Meaningful actual utilization. * @return The lender interest margin value. */ function _lenderInterestMargin( uint256 mau_ ) internal pure returns (uint256) { // Net Interest Margin = ((1 - MAU1)^(1/3) * 0.15) // Where MAU1 is MAU capped at 100% (min(MAU,1)) // Lender Interest Margin = 1 - Net Interest Margin // PRBMath library forbids raising a number < 1e18 to a power. Using the product and quotient rules of // exponents, rewrite the equation with a coefficient s which provides sufficient precision: // Net Interest Margin = ((1 - MAU1) * s)^(1/3) / s^(1/3) * 0.15 uint256 base = 1_000_000 * 1e18 - Maths.min(mau_, 1e18) * 1_000_000; // If unutilized deposit is infinitessimal, lenders get 100% of interest. if (base < 1e18) { return 1e18; } else { // cubic root of the percentage of meaningful unutilized deposit uint256 crpud = PRBMathUD60x18.pow(base, ONE_THIRD); // finish calculating Net Interest Margin, and then convert to Lender Interest Margin return 1e18 - Maths.wdiv(Maths.wmul(crpud, 0.15 * 1e18), CUBIC_ROOT_1000000); } } /** * @notice Calculates pool's meaningful deposit. * @param deposits_ Struct for pool deposits state. * @param t0DebtInAuction_ Value of pool's t0 debt currently in auction. * @param nonAuctionedT0Debt_ Value of pool's t0 debt that is not in auction. * @param inflator_ Pool's current inflator. * @param t0Debt2ToCollateral_ `t0Debt2ToCollateral` ratio. * @return meaningfulDeposit_ Pool's meaningful deposit. */ function _meaningfulDeposit( DepositsState storage deposits_, uint256 t0DebtInAuction_, uint256 nonAuctionedT0Debt_, uint256 inflator_, uint256 t0Debt2ToCollateral_ ) internal view returns (uint256 meaningfulDeposit_) { uint256 dwatp = _dwatp(nonAuctionedT0Debt_, inflator_, t0Debt2ToCollateral_); if (dwatp == 0) { meaningfulDeposit_ = Deposits.treeSum(deposits_); } else { if (dwatp >= MAX_PRICE) meaningfulDeposit_ = 0; else if (dwatp >= MIN_PRICE) meaningfulDeposit_ = Deposits.prefixSum(deposits_, _indexOf(dwatp)); else meaningfulDeposit_ = Deposits.treeSum(deposits_); } meaningfulDeposit_ -= Maths.min( meaningfulDeposit_, Maths.wmul(t0DebtInAuction_, inflator_) ); } /**********************/ /*** View Functions ***/ /**********************/ /** * @notice Calculates pool related debt values. * @param poolBalances_ Pool debt * @param inflatorState_ Interest inflator and last update time * @param interestState_ Interest rate and t0Debt2ToCollateral accumulator * @return Current amount of debt owed by borrowers in pool. * @return Debt owed by borrowers based on last inflator snapshot. * @return Total amount of debt in auction. * @return t0debt accross all borrowers divided by their collateral, used in determining a collateralization weighted debt. */ function debtInfo( PoolBalancesState memory poolBalances_, InflatorState memory inflatorState_, InterestState memory interestState_ ) external view returns (uint256, uint256, uint256, uint256) { uint256 t0Debt = poolBalances_.t0Debt; uint256 inflator = inflatorState_.inflator; return ( Maths.ceilWmul( t0Debt, pendingInflator(inflator, inflatorState_.inflatorUpdate, interestState_.interestRate) ), Maths.ceilWmul(t0Debt, inflator), Maths.ceilWmul(poolBalances_.t0DebtInAuction, inflator), interestState_.t0Debt2ToCollateral ); } /** * @notice Calculates pool interest factor for a given interest rate and time elapsed since last inflator update. * @param interestRate_ Current pool interest rate. * @param elapsed_ Time elapsed since last inflator update. * @return The value of pool interest factor. */ function pendingInterestFactor( uint256 interestRate_, uint256 elapsed_ ) external pure returns (uint256) { return PRBMathUD60x18.exp((interestRate_ * elapsed_) / 365 days); } /** * @notice Calculates pool pending inflator given the current inflator, time of last update and current interest rate. * @param inflator_ Current pool inflator. * @param inflatorUpdate Timestamp when inflator was updated. * @param interestRate_ The interest rate of the pool. * @return The pending value of pool inflator. */ function pendingInflator( uint256 inflator_, uint256 inflatorUpdate, uint256 interestRate_ ) public view returns (uint256) { return Maths.wmul( inflator_, PRBMathUD60x18.exp((interestRate_ * (block.timestamp - inflatorUpdate)) / 365 days) ); } /** * @notice Calculates lender interest margin for a given meaningful actual utilization. * @dev Wrapper of the internal function. */ function lenderInterestMargin( uint256 mau_ ) external pure returns (uint256) { return _lenderInterestMargin(mau_); } /** * @notice Calculates pool meaningful actual utilization. * @dev Wrapper of the internal function. */ function utilization( EmaState storage emaParams_ ) external view returns (uint256 utilization_) { return _utilization(emaParams_.debtEma, emaParams_.depositEma); } }
// SPDX-License-Identifier: Unlicense pragma solidity >=0.8.4; import "./PRBMath.sol"; /// @title PRBMathSD59x18 /// @author Paul Razvan Berg /// @notice Smart contract library for advanced fixed-point math that works with int256 numbers considered to have 18 /// trailing decimals. We call this number representation signed 59.18-decimal fixed-point, since the numbers can have /// a sign and there can be up to 59 digits in the integer part and up to 18 decimals in the fractional part. The numbers /// are bound by the minimum and the maximum values permitted by the Solidity type int256. library PRBMathSD59x18 { /// @dev log2(e) as a signed 59.18-decimal fixed-point number. int256 internal constant LOG2_E = 1_442695040888963407; /// @dev Half the SCALE number. int256 internal constant HALF_SCALE = 5e17; /// @dev The maximum value a signed 59.18-decimal fixed-point number can have. int256 internal constant MAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967; /// @dev The maximum whole value a signed 59.18-decimal fixed-point number can have. int256 internal constant MAX_WHOLE_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_000000000000000000; /// @dev The minimum value a signed 59.18-decimal fixed-point number can have. int256 internal constant MIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968; /// @dev The minimum whole value a signed 59.18-decimal fixed-point number can have. int256 internal constant MIN_WHOLE_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_000000000000000000; /// @dev How many trailing decimals can be represented. int256 internal constant SCALE = 1e18; /// INTERNAL FUNCTIONS /// /// @notice Calculate the absolute value of x. /// /// @dev Requirements: /// - x must be greater than MIN_SD59x18. /// /// @param x The number to calculate the absolute value for. /// @param result The absolute value of x. function abs(int256 x) internal pure returns (int256 result) { unchecked { if (x == MIN_SD59x18) { revert PRBMathSD59x18__AbsInputTooSmall(); } result = x < 0 ? -x : x; } } /// @notice Calculates the arithmetic average of x and y, rounding down. /// @param x The first operand as a signed 59.18-decimal fixed-point number. /// @param y The second operand as a signed 59.18-decimal fixed-point number. /// @return result The arithmetic average as a signed 59.18-decimal fixed-point number. function avg(int256 x, int256 y) internal pure returns (int256 result) { // The operations can never overflow. unchecked { int256 sum = (x >> 1) + (y >> 1); if (sum < 0) { // If at least one of x and y is odd, we add 1 to the result. This is because shifting negative numbers to the // right rounds down to infinity. assembly { result := add(sum, and(or(x, y), 1)) } } else { // If both x and y are odd, we add 1 to the result. This is because if both numbers are odd, the 0.5 // remainder gets truncated twice. result = sum + (x & y & 1); } } } /// @notice Yields the least greatest signed 59.18 decimal fixed-point number greater than or equal to x. /// /// @dev 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 must be less than or equal to MAX_WHOLE_SD59x18. /// /// @param x The signed 59.18-decimal fixed-point number to ceil. /// @param result The least integer greater than or equal to x, as a signed 58.18-decimal fixed-point number. function ceil(int256 x) internal pure returns (int256 result) { if (x > MAX_WHOLE_SD59x18) { revert PRBMathSD59x18__CeilOverflow(x); } unchecked { int256 remainder = x % SCALE; if (remainder == 0) { result = x; } else { // Solidity uses C fmod style, which returns a modulus with the same sign as x. result = x - remainder; if (x > 0) { result += SCALE; } } } } /// @notice Divides two signed 59.18-decimal fixed-point numbers, returning a new signed 59.18-decimal fixed-point number. /// /// @dev Variant of "mulDiv" that works with signed numbers. Works by computing the signs and the absolute values separately. /// /// Requirements: /// - All from "PRBMath.mulDiv". /// - None of the inputs can be MIN_SD59x18. /// - The denominator cannot be zero. /// - The result must fit within int256. /// /// Caveats: /// - All from "PRBMath.mulDiv". /// /// @param x The numerator as a signed 59.18-decimal fixed-point number. /// @param y The denominator as a signed 59.18-decimal fixed-point number. /// @param result The quotient as a signed 59.18-decimal fixed-point number. function div(int256 x, int256 y) internal pure returns (int256 result) { if (x == MIN_SD59x18 || y == MIN_SD59x18) { revert PRBMathSD59x18__DivInputTooSmall(); } // Get hold of the absolute values of x and y. uint256 ax; uint256 ay; unchecked { ax = x < 0 ? uint256(-x) : uint256(x); ay = y < 0 ? uint256(-y) : uint256(y); } // Compute the absolute value of (x*SCALE)÷y. The result must fit within int256. uint256 rAbs = PRBMath.mulDiv(ax, uint256(SCALE), ay); if (rAbs > uint256(MAX_SD59x18)) { revert PRBMathSD59x18__DivOverflow(rAbs); } // Get the signs of x and y. uint256 sx; uint256 sy; assembly { sx := sgt(x, sub(0, 1)) sy := sgt(y, sub(0, 1)) } // XOR over sx and sy. This is basically checking whether the inputs have the same sign. If yes, the result // should be positive. Otherwise, it should be negative. result = sx ^ sy == 1 ? -int256(rAbs) : int256(rAbs); } /// @notice Returns Euler's number as a signed 59.18-decimal fixed-point number. /// @dev See https://en.wikipedia.org/wiki/E_(mathematical_constant). function e() internal pure returns (int256 result) { result = 2_718281828459045235; } /// @notice Calculates the natural exponent of x. /// /// @dev Based on the insight that e^x = 2^(x * log2(e)). /// /// Requirements: /// - All from "log2". /// - x must be less than 133.084258667509499441. /// /// Caveats: /// - All from "exp2". /// - For any x less than -41.446531673892822322, the result is zero. /// /// @param x The exponent as a signed 59.18-decimal fixed-point number. /// @return result The result as a signed 59.18-decimal fixed-point number. function exp(int256 x) internal pure returns (int256 result) { // Without this check, the value passed to "exp2" would be less than -59.794705707972522261. if (x < -41_446531673892822322) { return 0; } // Without this check, the value passed to "exp2" would be greater than 192. if (x >= 133_084258667509499441) { revert PRBMathSD59x18__ExpInputTooBig(x); } // Do the fixed-point multiplication inline to save gas. unchecked { int256 doubleScaleProduct = x * LOG2_E; result = exp2((doubleScaleProduct + HALF_SCALE) / SCALE); } } /// @notice Calculates the binary exponent of x using the binary fraction method. /// /// @dev See https://ethereum.stackexchange.com/q/79903/24693. /// /// Requirements: /// - x must be 192 or less. /// - The result must fit within MAX_SD59x18. /// /// Caveats: /// - For any x less than -59.794705707972522261, the result is zero. /// /// @param x The exponent as a signed 59.18-decimal fixed-point number. /// @return result The result as a signed 59.18-decimal fixed-point number. function exp2(int256 x) internal pure returns (int256 result) { // This works because 2^(-x) = 1/2^x. if (x < 0) { // 2^59.794705707972522262 is the maximum number whose inverse does not truncate down to zero. if (x < -59_794705707972522261) { return 0; } // Do the fixed-point inversion inline to save gas. The numerator is SCALE * SCALE. unchecked { result = 1e36 / exp2(-x); } } else { // 2^192 doesn't fit within the 192.64-bit format used internally in this function. if (x >= 192e18) { revert PRBMathSD59x18__Exp2InputTooBig(x); } unchecked { // Convert x to the 192.64-bit fixed-point format. uint256 x192x64 = (uint256(x) << 64) / uint256(SCALE); // Safe to convert the result to int256 directly because the maximum input allowed is 192. result = int256(PRBMath.exp2(x192x64)); } } } /// @notice Yields the greatest signed 59.18 decimal fixed-point number less than or equal to x. /// /// @dev 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 must be greater than or equal to MIN_WHOLE_SD59x18. /// /// @param x The signed 59.18-decimal fixed-point number to floor. /// @param result The greatest integer less than or equal to x, as a signed 58.18-decimal fixed-point number. function floor(int256 x) internal pure returns (int256 result) { if (x < MIN_WHOLE_SD59x18) { revert PRBMathSD59x18__FloorUnderflow(x); } unchecked { int256 remainder = x % SCALE; if (remainder == 0) { result = x; } else { // Solidity uses C fmod style, which returns a modulus with the same sign as x. result = x - remainder; if (x < 0) { result -= SCALE; } } } } /// @notice Yields the excess beyond the floor of x for positive numbers and the part of the number to the right /// of the radix point for negative numbers. /// @dev Based on the odd function definition. https://en.wikipedia.org/wiki/Fractional_part /// @param x The signed 59.18-decimal fixed-point number to get the fractional part of. /// @param result The fractional part of x as a signed 59.18-decimal fixed-point number. function frac(int256 x) internal pure returns (int256 result) { unchecked { result = x % SCALE; } } /// @notice Converts a number from basic integer form to signed 59.18-decimal fixed-point representation. /// /// @dev Requirements: /// - x must be greater than or equal to MIN_SD59x18 divided by SCALE. /// - x must be less than or equal to MAX_SD59x18 divided by SCALE. /// /// @param x The basic integer to convert. /// @param result The same number in signed 59.18-decimal fixed-point representation. function fromInt(int256 x) internal pure returns (int256 result) { unchecked { if (x < MIN_SD59x18 / SCALE) { revert PRBMathSD59x18__FromIntUnderflow(x); } if (x > MAX_SD59x18 / SCALE) { revert PRBMathSD59x18__FromIntOverflow(x); } result = x * SCALE; } } /// @notice Calculates geometric mean of x and y, i.e. sqrt(x * y), rounding down. /// /// @dev Requirements: /// - x * y must fit within MAX_SD59x18, lest it overflows. /// - x * y cannot be negative. /// /// @param x The first operand as a signed 59.18-decimal fixed-point number. /// @param y The second operand as a signed 59.18-decimal fixed-point number. /// @return result The result as a signed 59.18-decimal fixed-point number. function gm(int256 x, int256 y) internal pure returns (int256 result) { if (x == 0) { return 0; } unchecked { // Checking for overflow this way is faster than letting Solidity do it. int256 xy = x * y; if (xy / x != y) { revert PRBMathSD59x18__GmOverflow(x, y); } // The product cannot be negative. if (xy < 0) { revert PRBMathSD59x18__GmNegativeProduct(x, y); } // We don't need to multiply by the SCALE here because the x*y product had already picked up a factor of SCALE // during multiplication. See the comments within the "sqrt" function. result = int256(PRBMath.sqrt(uint256(xy))); } } /// @notice Calculates 1 / x, rounding toward zero. /// /// @dev Requirements: /// - x cannot be zero. /// /// @param x The signed 59.18-decimal fixed-point number for which to calculate the inverse. /// @return result The inverse as a signed 59.18-decimal fixed-point number. function inv(int256 x) internal pure returns (int256 result) { unchecked { // 1e36 is SCALE * SCALE. result = 1e36 / x; } } /// @notice Calculates the natural logarithm of x. /// /// @dev Based on the insight that ln(x) = log2(x) / log2(e). /// /// Requirements: /// - All from "log2". /// /// Caveats: /// - All from "log2". /// - This doesn't return exactly 1 for 2718281828459045235, for that we would need more fine-grained precision. /// /// @param x The signed 59.18-decimal fixed-point number for which to calculate the natural logarithm. /// @return result The natural logarithm as a signed 59.18-decimal fixed-point number. function ln(int256 x) internal pure returns (int256 result) { // Do the fixed-point multiplication inline to save gas. This is overflow-safe because the maximum value that log2(x) // can return is 195205294292027477728. unchecked { result = (log2(x) * SCALE) / LOG2_E; } } /// @notice Calculates the common logarithm of x. /// /// @dev First checks if x is an exact power of ten and it stops if yes. If it's not, calculates the common /// logarithm based on the insight that log10(x) = log2(x) / log2(10). /// /// Requirements: /// - All from "log2". /// /// Caveats: /// - All from "log2". /// /// @param x The signed 59.18-decimal fixed-point number for which to calculate the common logarithm. /// @return result The common logarithm as a signed 59.18-decimal fixed-point number. function log10(int256 x) internal pure returns (int256 result) { if (x <= 0) { revert PRBMathSD59x18__LogInputTooSmall(x); } // Note that the "mul" in this block is the assembly mul operation, not the "mul" function defined in this contract. // prettier-ignore assembly { switch x case 1 { result := mul(SCALE, sub(0, 18)) } case 10 { result := mul(SCALE, sub(1, 18)) } case 100 { result := mul(SCALE, sub(2, 18)) } case 1000 { result := mul(SCALE, sub(3, 18)) } case 10000 { result := mul(SCALE, sub(4, 18)) } case 100000 { result := mul(SCALE, sub(5, 18)) } case 1000000 { result := mul(SCALE, sub(6, 18)) } case 10000000 { result := mul(SCALE, sub(7, 18)) } case 100000000 { result := mul(SCALE, sub(8, 18)) } case 1000000000 { result := mul(SCALE, sub(9, 18)) } case 10000000000 { result := mul(SCALE, sub(10, 18)) } case 100000000000 { result := mul(SCALE, sub(11, 18)) } case 1000000000000 { result := mul(SCALE, sub(12, 18)) } case 10000000000000 { result := mul(SCALE, sub(13, 18)) } case 100000000000000 { result := mul(SCALE, sub(14, 18)) } case 1000000000000000 { result := mul(SCALE, sub(15, 18)) } case 10000000000000000 { result := mul(SCALE, sub(16, 18)) } case 100000000000000000 { result := mul(SCALE, sub(17, 18)) } case 1000000000000000000 { result := 0 } case 10000000000000000000 { result := SCALE } case 100000000000000000000 { result := mul(SCALE, 2) } case 1000000000000000000000 { result := mul(SCALE, 3) } case 10000000000000000000000 { result := mul(SCALE, 4) } case 100000000000000000000000 { result := mul(SCALE, 5) } case 1000000000000000000000000 { result := mul(SCALE, 6) } case 10000000000000000000000000 { result := mul(SCALE, 7) } case 100000000000000000000000000 { result := mul(SCALE, 8) } case 1000000000000000000000000000 { result := mul(SCALE, 9) } case 10000000000000000000000000000 { result := mul(SCALE, 10) } case 100000000000000000000000000000 { result := mul(SCALE, 11) } case 1000000000000000000000000000000 { result := mul(SCALE, 12) } case 10000000000000000000000000000000 { result := mul(SCALE, 13) } case 100000000000000000000000000000000 { result := mul(SCALE, 14) } case 1000000000000000000000000000000000 { result := mul(SCALE, 15) } case 10000000000000000000000000000000000 { result := mul(SCALE, 16) } case 100000000000000000000000000000000000 { result := mul(SCALE, 17) } case 1000000000000000000000000000000000000 { result := mul(SCALE, 18) } case 10000000000000000000000000000000000000 { result := mul(SCALE, 19) } case 100000000000000000000000000000000000000 { result := mul(SCALE, 20) } case 1000000000000000000000000000000000000000 { result := mul(SCALE, 21) } case 10000000000000000000000000000000000000000 { result := mul(SCALE, 22) } case 100000000000000000000000000000000000000000 { result := mul(SCALE, 23) } case 1000000000000000000000000000000000000000000 { result := mul(SCALE, 24) } case 10000000000000000000000000000000000000000000 { result := mul(SCALE, 25) } case 100000000000000000000000000000000000000000000 { result := mul(SCALE, 26) } case 1000000000000000000000000000000000000000000000 { result := mul(SCALE, 27) } case 10000000000000000000000000000000000000000000000 { result := mul(SCALE, 28) } case 100000000000000000000000000000000000000000000000 { result := mul(SCALE, 29) } case 1000000000000000000000000000000000000000000000000 { result := mul(SCALE, 30) } case 10000000000000000000000000000000000000000000000000 { result := mul(SCALE, 31) } case 100000000000000000000000000000000000000000000000000 { result := mul(SCALE, 32) } case 1000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 33) } case 10000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 34) } case 100000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 35) } case 1000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 36) } case 10000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 37) } case 100000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 38) } case 1000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 39) } case 10000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 40) } case 100000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 41) } case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 42) } case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 43) } case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 44) } case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 45) } case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 46) } case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 47) } case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 48) } case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 49) } case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 50) } case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 51) } case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 52) } case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 53) } case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 54) } case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 55) } case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 56) } case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 57) } case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 58) } default { result := MAX_SD59x18 } } if (result == MAX_SD59x18) { // Do the fixed-point division inline to save gas. The denominator is log2(10). unchecked { result = (log2(x) * SCALE) / 3_321928094887362347; } } } /// @notice Calculates the binary logarithm of x. /// /// @dev Based on the iterative approximation algorithm. /// https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation /// /// Requirements: /// - x must be greater than zero. /// /// Caveats: /// - The results are not perfectly accurate to the last decimal, due to the lossy precision of the iterative approximation. /// /// @param x The signed 59.18-decimal fixed-point number for which to calculate the binary logarithm. /// @return result The binary logarithm as a signed 59.18-decimal fixed-point number. function log2(int256 x) internal pure returns (int256 result) { if (x <= 0) { revert PRBMathSD59x18__LogInputTooSmall(x); } unchecked { // This works because log2(x) = -log2(1/x). int256 sign; if (x >= SCALE) { sign = 1; } else { sign = -1; // Do the fixed-point inversion inline to save gas. The numerator is SCALE * SCALE. assembly { x := div(1000000000000000000000000000000000000, x) } } // Calculate the integer part of the logarithm and add it to the result and finally calculate y = x * 2^(-n). uint256 n = PRBMath.mostSignificantBit(uint256(x / SCALE)); // The integer part of the logarithm as a signed 59.18-decimal fixed-point number. The operation can't overflow // because n is maximum 255, SCALE is 1e18 and sign is either 1 or -1. result = int256(n) * SCALE; // This is y = x * 2^(-n). int256 y = x >> n; // If y = 1, the fractional part is zero. if (y == SCALE) { return result * sign; } // Calculate the fractional part via the iterative approximation. // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. for (int256 delta = int256(HALF_SCALE); delta > 0; delta >>= 1) { y = (y * y) / SCALE; // Is y^2 > 2 and so in the range [2,4)? if (y >= 2 * SCALE) { // Add the 2^(-m) factor to the logarithm. result += delta; // Corresponds to z/2 on Wikipedia. y >>= 1; } } result *= sign; } } /// @notice Multiplies two signed 59.18-decimal fixed-point numbers together, returning a new signed 59.18-decimal /// fixed-point number. /// /// @dev Variant of "mulDiv" that works with signed numbers and employs constant folding, i.e. the denominator is /// always 1e18. /// /// Requirements: /// - All from "PRBMath.mulDivFixedPoint". /// - None of the inputs can be MIN_SD59x18 /// - The result must fit within MAX_SD59x18. /// /// Caveats: /// - The body is purposely left uncommented; see the NatSpec comments in "PRBMath.mulDiv" to understand how this works. /// /// @param x The multiplicand as a signed 59.18-decimal fixed-point number. /// @param y The multiplier as a signed 59.18-decimal fixed-point number. /// @return result The product as a signed 59.18-decimal fixed-point number. function mul(int256 x, int256 y) internal pure returns (int256 result) { if (x == MIN_SD59x18 || y == MIN_SD59x18) { revert PRBMathSD59x18__MulInputTooSmall(); } unchecked { uint256 ax; uint256 ay; ax = x < 0 ? uint256(-x) : uint256(x); ay = y < 0 ? uint256(-y) : uint256(y); uint256 rAbs = PRBMath.mulDivFixedPoint(ax, ay); if (rAbs > uint256(MAX_SD59x18)) { revert PRBMathSD59x18__MulOverflow(rAbs); } uint256 sx; uint256 sy; assembly { sx := sgt(x, sub(0, 1)) sy := sgt(y, sub(0, 1)) } result = sx ^ sy == 1 ? -int256(rAbs) : int256(rAbs); } } /// @notice Returns PI as a signed 59.18-decimal fixed-point number. function pi() internal pure returns (int256 result) { result = 3_141592653589793238; } /// @notice Raises x to the power of y. /// /// @dev Based on the insight that x^y = 2^(log2(x) * y). /// /// Requirements: /// - All from "exp2", "log2" and "mul". /// - z cannot be zero. /// /// Caveats: /// - All from "exp2", "log2" and "mul". /// - Assumes 0^0 is 1. /// /// @param x Number to raise to given power y, as a signed 59.18-decimal fixed-point number. /// @param y Exponent to raise x to, as a signed 59.18-decimal fixed-point number. /// @return result x raised to power y, as a signed 59.18-decimal fixed-point number. function pow(int256 x, int256 y) internal pure returns (int256 result) { if (x == 0) { result = y == 0 ? SCALE : int256(0); } else { result = exp2(mul(log2(x), y)); } } /// @notice Raises x (signed 59.18-decimal fixed-point number) to the power of y (basic unsigned integer) using the /// famous algorithm "exponentiation by squaring". /// /// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring /// /// Requirements: /// - All from "abs" and "PRBMath.mulDivFixedPoint". /// - The result must fit within MAX_SD59x18. /// /// Caveats: /// - All from "PRBMath.mulDivFixedPoint". /// - Assumes 0^0 is 1. /// /// @param x The base as a signed 59.18-decimal fixed-point number. /// @param y The exponent as an uint256. /// @return result The result as a signed 59.18-decimal fixed-point number. function powu(int256 x, uint256 y) internal pure returns (int256 result) { uint256 xAbs = uint256(abs(x)); // Calculate the first iteration of the loop in advance. uint256 rAbs = y & 1 > 0 ? xAbs : uint256(SCALE); // Equivalent to "for(y /= 2; y > 0; y /= 2)" but faster. uint256 yAux = y; for (yAux >>= 1; yAux > 0; yAux >>= 1) { xAbs = PRBMath.mulDivFixedPoint(xAbs, xAbs); // Equivalent to "y % 2 == 1" but faster. if (yAux & 1 > 0) { rAbs = PRBMath.mulDivFixedPoint(rAbs, xAbs); } } // The result must fit within the 59.18-decimal fixed-point representation. if (rAbs > uint256(MAX_SD59x18)) { revert PRBMathSD59x18__PowuOverflow(rAbs); } // Is the base negative and the exponent an odd number? bool isNegative = x < 0 && y & 1 == 1; result = isNegative ? -int256(rAbs) : int256(rAbs); } /// @notice Returns 1 as a signed 59.18-decimal fixed-point number. function scale() internal pure returns (int256 result) { result = SCALE; } /// @notice Calculates the square root of x, rounding down. /// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method. /// /// Requirements: /// - x cannot be negative. /// - x must be less than MAX_SD59x18 / SCALE. /// /// @param x The signed 59.18-decimal fixed-point number for which to calculate the square root. /// @return result The result as a signed 59.18-decimal fixed-point . function sqrt(int256 x) internal pure returns (int256 result) { unchecked { if (x < 0) { revert PRBMathSD59x18__SqrtNegativeInput(x); } if (x > MAX_SD59x18 / SCALE) { revert PRBMathSD59x18__SqrtOverflow(x); } // Multiply x by the SCALE to account for the factor of SCALE that is picked up when multiplying two signed // 59.18-decimal fixed-point numbers together (in this case, those two numbers are both the square root). result = int256(PRBMath.sqrt(uint256(x * SCALE))); } } /// @notice Converts a signed 59.18-decimal fixed-point number to basic integer form, rounding down in the process. /// @param x The signed 59.18-decimal fixed-point number to convert. /// @return result The same number in basic integer form. function toInt(int256 x) internal pure returns (int256 result) { unchecked { result = x / SCALE; } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol) pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { enum Rounding { Down, // Toward negative infinity Up, // Toward infinity Zero // Toward zero } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds up instead * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ function mulDiv( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. require(denominator > prod1); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv( uint256 x, uint256 y, uint256 denominator, Rounding rounding ) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10**64) { value /= 10**64; result += 64; } if (value >= 10**32) { value /= 10**32; result += 32; } if (value >= 10**16) { value /= 10**16; result += 16; } if (value >= 10**8) { value /= 10**8; result += 8; } if (value >= 10**4) { value /= 10**4; result += 4; } if (value >= 10**2) { value /= 10**2; result += 2; } if (value >= 10**1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0); } } /** * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.0; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. * * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits * * _Available since v4.7._ */ function toUint248(uint256 value) internal pure returns (uint248) { require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits"); return uint248(value); } /** * @dev Returns the downcasted uint240 from uint256, reverting on * overflow (when the input is greater than largest uint240). * * Counterpart to Solidity's `uint240` operator. * * Requirements: * * - input must fit into 240 bits * * _Available since v4.7._ */ function toUint240(uint256 value) internal pure returns (uint240) { require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits"); return uint240(value); } /** * @dev Returns the downcasted uint232 from uint256, reverting on * overflow (when the input is greater than largest uint232). * * Counterpart to Solidity's `uint232` operator. * * Requirements: * * - input must fit into 232 bits * * _Available since v4.7._ */ function toUint232(uint256 value) internal pure returns (uint232) { require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits"); return uint232(value); } /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). * * Counterpart to Solidity's `uint224` operator. * * Requirements: * * - input must fit into 224 bits * * _Available since v4.2._ */ function toUint224(uint256 value) internal pure returns (uint224) { require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); return uint224(value); } /** * @dev Returns the downcasted uint216 from uint256, reverting on * overflow (when the input is greater than largest uint216). * * Counterpart to Solidity's `uint216` operator. * * Requirements: * * - input must fit into 216 bits * * _Available since v4.7._ */ function toUint216(uint256 value) internal pure returns (uint216) { require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits"); return uint216(value); } /** * @dev Returns the downcasted uint208 from uint256, reverting on * overflow (when the input is greater than largest uint208). * * Counterpart to Solidity's `uint208` operator. * * Requirements: * * - input must fit into 208 bits * * _Available since v4.7._ */ function toUint208(uint256 value) internal pure returns (uint208) { require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits"); return uint208(value); } /** * @dev Returns the downcasted uint200 from uint256, reverting on * overflow (when the input is greater than largest uint200). * * Counterpart to Solidity's `uint200` operator. * * Requirements: * * - input must fit into 200 bits * * _Available since v4.7._ */ function toUint200(uint256 value) internal pure returns (uint200) { require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits"); return uint200(value); } /** * @dev Returns the downcasted uint192 from uint256, reverting on * overflow (when the input is greater than largest uint192). * * Counterpart to Solidity's `uint192` operator. * * Requirements: * * - input must fit into 192 bits * * _Available since v4.7._ */ function toUint192(uint256 value) internal pure returns (uint192) { require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits"); return uint192(value); } /** * @dev Returns the downcasted uint184 from uint256, reverting on * overflow (when the input is greater than largest uint184). * * Counterpart to Solidity's `uint184` operator. * * Requirements: * * - input must fit into 184 bits * * _Available since v4.7._ */ function toUint184(uint256 value) internal pure returns (uint184) { require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits"); return uint184(value); } /** * @dev Returns the downcasted uint176 from uint256, reverting on * overflow (when the input is greater than largest uint176). * * Counterpart to Solidity's `uint176` operator. * * Requirements: * * - input must fit into 176 bits * * _Available since v4.7._ */ function toUint176(uint256 value) internal pure returns (uint176) { require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits"); return uint176(value); } /** * @dev Returns the downcasted uint168 from uint256, reverting on * overflow (when the input is greater than largest uint168). * * Counterpart to Solidity's `uint168` operator. * * Requirements: * * - input must fit into 168 bits * * _Available since v4.7._ */ function toUint168(uint256 value) internal pure returns (uint168) { require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits"); return uint168(value); } /** * @dev Returns the downcasted uint160 from uint256, reverting on * overflow (when the input is greater than largest uint160). * * Counterpart to Solidity's `uint160` operator. * * Requirements: * * - input must fit into 160 bits * * _Available since v4.7._ */ function toUint160(uint256 value) internal pure returns (uint160) { require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits"); return uint160(value); } /** * @dev Returns the downcasted uint152 from uint256, reverting on * overflow (when the input is greater than largest uint152). * * Counterpart to Solidity's `uint152` operator. * * Requirements: * * - input must fit into 152 bits * * _Available since v4.7._ */ function toUint152(uint256 value) internal pure returns (uint152) { require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits"); return uint152(value); } /** * @dev Returns the downcasted uint144 from uint256, reverting on * overflow (when the input is greater than largest uint144). * * Counterpart to Solidity's `uint144` operator. * * Requirements: * * - input must fit into 144 bits * * _Available since v4.7._ */ function toUint144(uint256 value) internal pure returns (uint144) { require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits"); return uint144(value); } /** * @dev Returns the downcasted uint136 from uint256, reverting on * overflow (when the input is greater than largest uint136). * * Counterpart to Solidity's `uint136` operator. * * Requirements: * * - input must fit into 136 bits * * _Available since v4.7._ */ function toUint136(uint256 value) internal pure returns (uint136) { require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits"); return uint136(value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits * * _Available since v2.5._ */ function toUint128(uint256 value) internal pure returns (uint128) { require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); return uint128(value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits * * _Available since v4.7._ */ function toUint120(uint256 value) internal pure returns (uint120) { require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits"); return uint120(value); } /** * @dev Returns the downcasted uint112 from uint256, reverting on * overflow (when the input is greater than largest uint112). * * Counterpart to Solidity's `uint112` operator. * * Requirements: * * - input must fit into 112 bits * * _Available since v4.7._ */ function toUint112(uint256 value) internal pure returns (uint112) { require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits"); return uint112(value); } /** * @dev Returns the downcasted uint104 from uint256, reverting on * overflow (when the input is greater than largest uint104). * * Counterpart to Solidity's `uint104` operator. * * Requirements: * * - input must fit into 104 bits * * _Available since v4.7._ */ function toUint104(uint256 value) internal pure returns (uint104) { require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); return uint104(value); } /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). * * Counterpart to Solidity's `uint96` operator. * * Requirements: * * - input must fit into 96 bits * * _Available since v4.2._ */ function toUint96(uint256 value) internal pure returns (uint96) { require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); return uint96(value); } /** * @dev Returns the downcasted uint88 from uint256, reverting on * overflow (when the input is greater than largest uint88). * * Counterpart to Solidity's `uint88` operator. * * Requirements: * * - input must fit into 88 bits * * _Available since v4.7._ */ function toUint88(uint256 value) internal pure returns (uint88) { require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); return uint88(value); } /** * @dev Returns the downcasted uint80 from uint256, reverting on * overflow (when the input is greater than largest uint80). * * Counterpart to Solidity's `uint80` operator. * * Requirements: * * - input must fit into 80 bits * * _Available since v4.7._ */ function toUint80(uint256 value) internal pure returns (uint80) { require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits"); return uint80(value); } /** * @dev Returns the downcasted uint72 from uint256, reverting on * overflow (when the input is greater than largest uint72). * * Counterpart to Solidity's `uint72` operator. * * Requirements: * * - input must fit into 72 bits * * _Available since v4.7._ */ function toUint72(uint256 value) internal pure returns (uint72) { require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits"); return uint72(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits * * _Available since v2.5._ */ function toUint64(uint256 value) internal pure returns (uint64) { require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); return uint64(value); } /** * @dev Returns the downcasted uint56 from uint256, reverting on * overflow (when the input is greater than largest uint56). * * Counterpart to Solidity's `uint56` operator. * * Requirements: * * - input must fit into 56 bits * * _Available since v4.7._ */ function toUint56(uint256 value) internal pure returns (uint56) { require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits"); return uint56(value); } /** * @dev Returns the downcasted uint48 from uint256, reverting on * overflow (when the input is greater than largest uint48). * * Counterpart to Solidity's `uint48` operator. * * Requirements: * * - input must fit into 48 bits * * _Available since v4.7._ */ function toUint48(uint256 value) internal pure returns (uint48) { require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits"); return uint48(value); } /** * @dev Returns the downcasted uint40 from uint256, reverting on * overflow (when the input is greater than largest uint40). * * Counterpart to Solidity's `uint40` operator. * * Requirements: * * - input must fit into 40 bits * * _Available since v4.7._ */ function toUint40(uint256 value) internal pure returns (uint40) { require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits"); return uint40(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits * * _Available since v2.5._ */ function toUint32(uint256 value) internal pure returns (uint32) { require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); return uint32(value); } /** * @dev Returns the downcasted uint24 from uint256, reverting on * overflow (when the input is greater than largest uint24). * * Counterpart to Solidity's `uint24` operator. * * Requirements: * * - input must fit into 24 bits * * _Available since v4.7._ */ function toUint24(uint256 value) internal pure returns (uint24) { require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); return uint24(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits * * _Available since v2.5._ */ function toUint16(uint256 value) internal pure returns (uint16) { require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits * * _Available since v2.5._ */ function toUint8(uint256 value) internal pure returns (uint8) { require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. * * _Available since v3.0._ */ function toUint256(int256 value) internal pure returns (uint256) { require(value >= 0, "SafeCast: value must be positive"); return uint256(value); } /** * @dev Returns the downcasted int248 from int256, reverting on * overflow (when the input is less than smallest int248 or * greater than largest int248). * * Counterpart to Solidity's `int248` operator. * * Requirements: * * - input must fit into 248 bits * * _Available since v4.7._ */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); require(downcasted == value, "SafeCast: value doesn't fit in 248 bits"); } /** * @dev Returns the downcasted int240 from int256, reverting on * overflow (when the input is less than smallest int240 or * greater than largest int240). * * Counterpart to Solidity's `int240` operator. * * Requirements: * * - input must fit into 240 bits * * _Available since v4.7._ */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); require(downcasted == value, "SafeCast: value doesn't fit in 240 bits"); } /** * @dev Returns the downcasted int232 from int256, reverting on * overflow (when the input is less than smallest int232 or * greater than largest int232). * * Counterpart to Solidity's `int232` operator. * * Requirements: * * - input must fit into 232 bits * * _Available since v4.7._ */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); require(downcasted == value, "SafeCast: value doesn't fit in 232 bits"); } /** * @dev Returns the downcasted int224 from int256, reverting on * overflow (when the input is less than smallest int224 or * greater than largest int224). * * Counterpart to Solidity's `int224` operator. * * Requirements: * * - input must fit into 224 bits * * _Available since v4.7._ */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); require(downcasted == value, "SafeCast: value doesn't fit in 224 bits"); } /** * @dev Returns the downcasted int216 from int256, reverting on * overflow (when the input is less than smallest int216 or * greater than largest int216). * * Counterpart to Solidity's `int216` operator. * * Requirements: * * - input must fit into 216 bits * * _Available since v4.7._ */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); require(downcasted == value, "SafeCast: value doesn't fit in 216 bits"); } /** * @dev Returns the downcasted int208 from int256, reverting on * overflow (when the input is less than smallest int208 or * greater than largest int208). * * Counterpart to Solidity's `int208` operator. * * Requirements: * * - input must fit into 208 bits * * _Available since v4.7._ */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); require(downcasted == value, "SafeCast: value doesn't fit in 208 bits"); } /** * @dev Returns the downcasted int200 from int256, reverting on * overflow (when the input is less than smallest int200 or * greater than largest int200). * * Counterpart to Solidity's `int200` operator. * * Requirements: * * - input must fit into 200 bits * * _Available since v4.7._ */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); require(downcasted == value, "SafeCast: value doesn't fit in 200 bits"); } /** * @dev Returns the downcasted int192 from int256, reverting on * overflow (when the input is less than smallest int192 or * greater than largest int192). * * Counterpart to Solidity's `int192` operator. * * Requirements: * * - input must fit into 192 bits * * _Available since v4.7._ */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); require(downcasted == value, "SafeCast: value doesn't fit in 192 bits"); } /** * @dev Returns the downcasted int184 from int256, reverting on * overflow (when the input is less than smallest int184 or * greater than largest int184). * * Counterpart to Solidity's `int184` operator. * * Requirements: * * - input must fit into 184 bits * * _Available since v4.7._ */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); require(downcasted == value, "SafeCast: value doesn't fit in 184 bits"); } /** * @dev Returns the downcasted int176 from int256, reverting on * overflow (when the input is less than smallest int176 or * greater than largest int176). * * Counterpart to Solidity's `int176` operator. * * Requirements: * * - input must fit into 176 bits * * _Available since v4.7._ */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); require(downcasted == value, "SafeCast: value doesn't fit in 176 bits"); } /** * @dev Returns the downcasted int168 from int256, reverting on * overflow (when the input is less than smallest int168 or * greater than largest int168). * * Counterpart to Solidity's `int168` operator. * * Requirements: * * - input must fit into 168 bits * * _Available since v4.7._ */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); require(downcasted == value, "SafeCast: value doesn't fit in 168 bits"); } /** * @dev Returns the downcasted int160 from int256, reverting on * overflow (when the input is less than smallest int160 or * greater than largest int160). * * Counterpart to Solidity's `int160` operator. * * Requirements: * * - input must fit into 160 bits * * _Available since v4.7._ */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); require(downcasted == value, "SafeCast: value doesn't fit in 160 bits"); } /** * @dev Returns the downcasted int152 from int256, reverting on * overflow (when the input is less than smallest int152 or * greater than largest int152). * * Counterpart to Solidity's `int152` operator. * * Requirements: * * - input must fit into 152 bits * * _Available since v4.7._ */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); require(downcasted == value, "SafeCast: value doesn't fit in 152 bits"); } /** * @dev Returns the downcasted int144 from int256, reverting on * overflow (when the input is less than smallest int144 or * greater than largest int144). * * Counterpart to Solidity's `int144` operator. * * Requirements: * * - input must fit into 144 bits * * _Available since v4.7._ */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); require(downcasted == value, "SafeCast: value doesn't fit in 144 bits"); } /** * @dev Returns the downcasted int136 from int256, reverting on * overflow (when the input is less than smallest int136 or * greater than largest int136). * * Counterpart to Solidity's `int136` operator. * * Requirements: * * - input must fit into 136 bits * * _Available since v4.7._ */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); require(downcasted == value, "SafeCast: value doesn't fit in 136 bits"); } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits * * _Available since v3.1._ */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); require(downcasted == value, "SafeCast: value doesn't fit in 128 bits"); } /** * @dev Returns the downcasted int120 from int256, reverting on * overflow (when the input is less than smallest int120 or * greater than largest int120). * * Counterpart to Solidity's `int120` operator. * * Requirements: * * - input must fit into 120 bits * * _Available since v4.7._ */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); require(downcasted == value, "SafeCast: value doesn't fit in 120 bits"); } /** * @dev Returns the downcasted int112 from int256, reverting on * overflow (when the input is less than smallest int112 or * greater than largest int112). * * Counterpart to Solidity's `int112` operator. * * Requirements: * * - input must fit into 112 bits * * _Available since v4.7._ */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); require(downcasted == value, "SafeCast: value doesn't fit in 112 bits"); } /** * @dev Returns the downcasted int104 from int256, reverting on * overflow (when the input is less than smallest int104 or * greater than largest int104). * * Counterpart to Solidity's `int104` operator. * * Requirements: * * - input must fit into 104 bits * * _Available since v4.7._ */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); require(downcasted == value, "SafeCast: value doesn't fit in 104 bits"); } /** * @dev Returns the downcasted int96 from int256, reverting on * overflow (when the input is less than smallest int96 or * greater than largest int96). * * Counterpart to Solidity's `int96` operator. * * Requirements: * * - input must fit into 96 bits * * _Available since v4.7._ */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); require(downcasted == value, "SafeCast: value doesn't fit in 96 bits"); } /** * @dev Returns the downcasted int88 from int256, reverting on * overflow (when the input is less than smallest int88 or * greater than largest int88). * * Counterpart to Solidity's `int88` operator. * * Requirements: * * - input must fit into 88 bits * * _Available since v4.7._ */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); require(downcasted == value, "SafeCast: value doesn't fit in 88 bits"); } /** * @dev Returns the downcasted int80 from int256, reverting on * overflow (when the input is less than smallest int80 or * greater than largest int80). * * Counterpart to Solidity's `int80` operator. * * Requirements: * * - input must fit into 80 bits * * _Available since v4.7._ */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); require(downcasted == value, "SafeCast: value doesn't fit in 80 bits"); } /** * @dev Returns the downcasted int72 from int256, reverting on * overflow (when the input is less than smallest int72 or * greater than largest int72). * * Counterpart to Solidity's `int72` operator. * * Requirements: * * - input must fit into 72 bits * * _Available since v4.7._ */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); require(downcasted == value, "SafeCast: value doesn't fit in 72 bits"); } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits * * _Available since v3.1._ */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); require(downcasted == value, "SafeCast: value doesn't fit in 64 bits"); } /** * @dev Returns the downcasted int56 from int256, reverting on * overflow (when the input is less than smallest int56 or * greater than largest int56). * * Counterpart to Solidity's `int56` operator. * * Requirements: * * - input must fit into 56 bits * * _Available since v4.7._ */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); require(downcasted == value, "SafeCast: value doesn't fit in 56 bits"); } /** * @dev Returns the downcasted int48 from int256, reverting on * overflow (when the input is less than smallest int48 or * greater than largest int48). * * Counterpart to Solidity's `int48` operator. * * Requirements: * * - input must fit into 48 bits * * _Available since v4.7._ */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); require(downcasted == value, "SafeCast: value doesn't fit in 48 bits"); } /** * @dev Returns the downcasted int40 from int256, reverting on * overflow (when the input is less than smallest int40 or * greater than largest int40). * * Counterpart to Solidity's `int40` operator. * * Requirements: * * - input must fit into 40 bits * * _Available since v4.7._ */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); require(downcasted == value, "SafeCast: value doesn't fit in 40 bits"); } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits * * _Available since v3.1._ */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); require(downcasted == value, "SafeCast: value doesn't fit in 32 bits"); } /** * @dev Returns the downcasted int24 from int256, reverting on * overflow (when the input is less than smallest int24 or * greater than largest int24). * * Counterpart to Solidity's `int24` operator. * * Requirements: * * - input must fit into 24 bits * * _Available since v4.7._ */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); require(downcasted == value, "SafeCast: value doesn't fit in 24 bits"); } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits * * _Available since v3.1._ */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); require(downcasted == value, "SafeCast: value doesn't fit in 16 bits"); } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits * * _Available since v3.1._ */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); require(downcasted == value, "SafeCast: value doesn't fit in 8 bits"); } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. * * _Available since v3.0._ */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256"); return int256(value); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { Bucket, Lender } from '../../interfaces/pool/commons/IPoolState.sol'; import { Maths } from './Maths.sol'; /** @title Buckets library @notice Internal library containing common logic for buckets management. */ library Buckets { /**************/ /*** Events ***/ /**************/ // See `IPoolError` for descriptions error BucketBankruptcyBlock(); /***********************************/ /*** Bucket Management Functions ***/ /***********************************/ /** * @notice Add collateral to a bucket and updates `LP` for bucket and lender with the amount coresponding to collateral amount added. * @dev Increment `bucket.collateral` and `bucket.lps` accumulator * @dev - `addLenderLP`: * @dev increment `lender.lps` accumulator and `lender.depositTime` state * @param lender_ Address of the lender. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param collateralAmountToAdd_ Additional collateral amount to add to bucket. * @param bucketPrice_ Bucket price. * @return addedLP_ Amount of bucket `LP` for the collateral amount added. */ function addCollateral( Bucket storage bucket_, address lender_, uint256 deposit_, uint256 collateralAmountToAdd_, uint256 bucketPrice_ ) internal returns (uint256 addedLP_) { // cannot deposit in the same block when bucket becomes insolvent uint256 bankruptcyTime = bucket_.bankruptcyTime; if (bankruptcyTime == block.timestamp) revert BucketBankruptcyBlock(); // calculate amount of LP to be added for the amount of collateral added to bucket addedLP_ = collateralToLP( bucket_.collateral, bucket_.lps, deposit_, collateralAmountToAdd_, bucketPrice_, Math.Rounding.Down ); // update bucket LP balance and collateral // update bucket collateral bucket_.collateral += collateralAmountToAdd_; // update bucket and lender LP balance and deposit timestamp bucket_.lps += addedLP_; addLenderLP(bucket_, bankruptcyTime, lender_, addedLP_); } /** * @notice Add amount of `LP` for a given lender in a given bucket. * @dev Increments lender lps accumulator and updates the deposit time. * @param bucket_ Bucket to record lender `LP`. * @param bankruptcyTime_ Time when bucket become insolvent. * @param lender_ Lender address to add `LP` for in the given bucket. * @param lpAmount_ Amount of `LP` to be recorded for the given lender. */ function addLenderLP( Bucket storage bucket_, uint256 bankruptcyTime_, address lender_, uint256 lpAmount_ ) internal { if (lpAmount_ != 0) { Lender storage lender = bucket_.lenders[lender_]; if (bankruptcyTime_ >= lender.depositTime) lender.lps = lpAmount_; else lender.lps += lpAmount_; lender.depositTime = block.timestamp; } } /**********************/ /*** View Functions ***/ /**********************/ /****************************/ /*** Assets to LP helpers ***/ /****************************/ /** * @notice Returns the amount of bucket `LP` calculated for the given amount of collateral. * @param bucketCollateral_ Amount of collateral in bucket. * @param bucketLP_ Amount of `LP` in bucket. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param collateral_ The amount of collateral to calculate bucket LP for. * @param bucketPrice_ Bucket's price. * @param rounding_ The direction of rounding when calculating LP (down when adding, up when removing collateral from pool). * @return Amount of `LP` calculated for the amount of collateral. */ function collateralToLP( uint256 bucketCollateral_, uint256 bucketLP_, uint256 deposit_, uint256 collateral_, uint256 bucketPrice_, Math.Rounding rounding_ ) internal pure returns (uint256) { // case when there's no deposit nor collateral in bucket if (deposit_ == 0 && bucketCollateral_ == 0) return Maths.wmul(collateral_, bucketPrice_); // case when there's deposit or collateral in bucket but no LP to cover if (bucketLP_ == 0) return Maths.wmul(collateral_, bucketPrice_); // case when there's deposit or collateral and bucket has LP balance return Math.mulDiv( bucketLP_, collateral_ * bucketPrice_, deposit_ * Maths.WAD + bucketCollateral_ * bucketPrice_, rounding_ ); } /** * @notice Returns the amount of `LP` calculated for the given amount of quote tokens. * @param bucketCollateral_ Amount of collateral in bucket. * @param bucketLP_ Amount of `LP` in bucket. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param quoteTokens_ The amount of quote tokens to calculate `LP` amount for. * @param bucketPrice_ Bucket's price. * @param rounding_ The direction of rounding when calculating LP (down when adding, up when removing quote tokens from pool). * @return The amount of `LP` coresponding to the given quote tokens in current bucket. */ function quoteTokensToLP( uint256 bucketCollateral_, uint256 bucketLP_, uint256 deposit_, uint256 quoteTokens_, uint256 bucketPrice_, Math.Rounding rounding_ ) internal pure returns (uint256) { // case when there's no deposit nor collateral in bucket if (deposit_ == 0 && bucketCollateral_ == 0) return quoteTokens_; // case when there's deposit or collateral in bucket but no LP to cover if (bucketLP_ == 0) return quoteTokens_; // case when there's deposit or collateral and bucket has LP balance return Math.mulDiv( bucketLP_, quoteTokens_ * Maths.WAD, deposit_ * Maths.WAD + bucketCollateral_ * bucketPrice_, rounding_ ); } /****************************/ /*** LP to Assets helpers ***/ /****************************/ /** * @notice Returns the amount of collateral calculated for the given amount of lp * @dev The value returned is not capped at collateral amount available in bucket. * @param bucketCollateral_ Amount of collateral in bucket. * @param bucketLP_ Amount of `LP` in bucket. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param lp_ The amount of LP to calculate collateral amount for. * @param bucketPrice_ Bucket's price. * @return The amount of collateral coresponding to the given `LP` in current bucket. */ function lpToCollateral( uint256 bucketCollateral_, uint256 bucketLP_, uint256 deposit_, uint256 lp_, uint256 bucketPrice_, Math.Rounding rounding_ ) internal pure returns (uint256) { // case when there's no deposit nor collateral in bucket if (deposit_ == 0 && bucketCollateral_ == 0) return Maths.wdiv(lp_, bucketPrice_); // case when there's deposit or collateral in bucket but no LP to cover if (bucketLP_ == 0) return Maths.wdiv(lp_, bucketPrice_); // case when there's deposit or collateral and bucket has LP balance return Math.mulDiv( deposit_ * Maths.WAD + bucketCollateral_ * bucketPrice_, lp_, bucketLP_ * bucketPrice_, rounding_ ); } /** * @notice Returns the amount of quote token (in value) calculated for the given amount of `LP`. * @dev The value returned is not capped at available bucket deposit. * @param bucketCollateral_ Amount of collateral in bucket. * @param bucketLP_ Amount of `LP` in bucket. * @param deposit_ Current bucket deposit (quote tokens). Used to calculate bucket's exchange rate / `LP`. * @param lp_ The amount of LP to calculate quote tokens amount for. * @param bucketPrice_ Bucket's price. * @return The amount coresponding to the given quote tokens in current bucket. */ function lpToQuoteTokens( uint256 bucketCollateral_, uint256 bucketLP_, uint256 deposit_, uint256 lp_, uint256 bucketPrice_, Math.Rounding rounding_ ) internal pure returns (uint256) { // case when there's no deposit nor collateral in bucket if (deposit_ == 0 && bucketCollateral_ == 0) return lp_; // case when there's deposit or collateral in bucket but no LP to cover if (bucketLP_ == 0) return lp_; // case when there's deposit or collateral and bucket has LP balance return Math.mulDiv( deposit_ * Maths.WAD + bucketCollateral_ * bucketPrice_, lp_, bucketLP_ * Maths.WAD, rounding_ ); } /****************************/ /*** Exchange Rate helper ***/ /****************************/ /** * @notice Returns the exchange rate for a given bucket (conversion of 1 lp to quote token). * @param bucketCollateral_ Amount of collateral in bucket. * @param bucketLP_ Amount of `LP` in bucket. * @param bucketDeposit_ The amount of quote tokens deposited in the given bucket. * @param bucketPrice_ Bucket's price. */ function getExchangeRate( uint256 bucketCollateral_, uint256 bucketLP_, uint256 bucketDeposit_, uint256 bucketPrice_ ) internal pure returns (uint256) { return lpToQuoteTokens( bucketCollateral_, bucketLP_, bucketDeposit_, Maths.WAD, bucketPrice_, Math.Rounding.Up ); } }
// SPDX-License-Identifier: BSD pragma solidity ^0.8.4; /// @title Clone /// @author zefram.eth /// @notice Provides helper functions for reading immutable args from calldata contract Clone { /// @notice Reads an immutable arg with type address /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgAddress(uint256 argOffset) internal pure returns (address arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := shr(0x60, calldataload(add(offset, argOffset))) } } /// @notice Reads an immutable arg with type uint256 /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgUint256(uint256 argOffset) internal pure returns (uint256 arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := calldataload(add(offset, argOffset)) } } /// @notice Reads a uint256 array stored in the immutable args. /// @param argOffset The offset of the arg in the packed data /// @param arrLen Number of elements in the array /// @return arr The array function _getArgUint256Array(uint256 argOffset, uint64 arrLen) internal pure returns (uint256[] memory arr) { uint256 offset = _getImmutableArgsOffset(); uint256 el; arr = new uint256[](arrLen); for (uint64 i = 0; i < arrLen; i++) { assembly { // solhint-disable-next-line no-inline-assembly el := calldataload(add(add(offset, argOffset), mul(i, 32))) } arr[i] = el; } return arr; } /// @notice Reads an immutable arg with type uint64 /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgUint64(uint256 argOffset) internal pure returns (uint64 arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := shr(0xc0, calldataload(add(offset, argOffset))) } } /// @notice Reads an immutable arg with type uint8 /// @param argOffset The offset of the arg in the packed data /// @return arg The arg value function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { uint256 offset = _getImmutableArgsOffset(); // solhint-disable-next-line no-inline-assembly assembly { arg := shr(0xf8, calldataload(add(offset, argOffset))) } } /// @return offset The offset of the packed immutable args in calldata function _getImmutableArgsOffset() internal pure returns (uint256 offset) { // solhint-disable-next-line no-inline-assembly assembly { offset := sub( calldatasize(), add(shr(240, calldataload(sub(calldatasize(), 2))), 2) ) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (utils/Multicall.sol) pragma solidity ^0.8.0; import "./Address.sol"; /** * @dev Provides a function to batch together multiple calls in a single external call. * * _Available since v4.1._ */ abstract contract Multicall { /** * @dev Receives and executes a batch of function calls on this contract. */ function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; i++) { results[i] = Address.functionDelegateCall(address(this), data[i]); } return results; } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { Bucket, Lender } from '../../interfaces/pool/commons/IPoolState.sol'; import { MAX_FENWICK_INDEX } from '../helpers/PoolHelper.sol'; import { Maths } from '../internal/Maths.sol'; /** @title LPActions library @notice External library containing logic for `LP` owners to: - `increase`/`decrease`/`revoke` `LP` allowance; `approve`/`revoke` `LP` transferors; `transfer` `LP` */ library LPActions { /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event ApproveLPTransferors(address indexed lender, address[] transferors); event RevokeLPTransferors(address indexed lender, address[] transferors); event IncreaseLPAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); event DecreaseLPAllowance(address indexed owner, address indexed spender, uint256[] indexes, uint256[] amounts); event RevokeLPAllowance(address indexed owner, address indexed spender, uint256[] indexes); event TransferLP(address owner, address newOwner, uint256[] indexes, uint256 lp); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error BucketBankruptcyBlock(); error InvalidAllowancesInput(); error InvalidIndex(); error NoAllowance(); error TransferorNotApproved(); error TransferToSameOwner(); /***************************/ /*** External Functions ***/ /***************************/ /** * @notice See `IPoolLPActions` for descriptions * @dev === Write state === * @dev increment `LP` allowances * @dev === Reverts on === * @dev invalid indexes and amounts input `InvalidAllowancesInput()` * @dev === Emit events === * @dev - `IncreaseLPAllowance` */ function increaseLPAllowance( mapping(uint256 => uint256) storage allowances_, address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external { uint256 indexesLength = indexes_.length; if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); uint256 index; for (uint256 i = 0; i < indexesLength; ) { index = indexes_[i]; allowances_[index] += amounts_[i]; unchecked { ++i; } } emit IncreaseLPAllowance( msg.sender, spender_, indexes_, amounts_ ); } /** * @notice See `IPoolLPActions` for descriptions * @dev === Write state === * @dev decrement `LP` allowances * @dev === Reverts on === * @dev invalid indexes and amounts input `InvalidAllowancesInput()` * @dev === Emit events === * @dev - `DecreaseLPAllowance` */ function decreaseLPAllowance( mapping(uint256 => uint256) storage allowances_, address spender_, uint256[] calldata indexes_, uint256[] calldata amounts_ ) external { uint256 indexesLength = indexes_.length; if (indexesLength != amounts_.length) revert InvalidAllowancesInput(); uint256 index; for (uint256 i = 0; i < indexesLength; ) { index = indexes_[i]; allowances_[index] -= amounts_[i]; unchecked { ++i; } } emit DecreaseLPAllowance( msg.sender, spender_, indexes_, amounts_ ); } /** * @notice See `IPoolLPActions` for descriptions * @dev === Write state === * @dev decrement `LP` allowances * @dev === Emit events === * @dev - `RevokeLPAllowance` */ function revokeLPAllowance( mapping(uint256 => uint256) storage allowances_, address spender_, uint256[] calldata indexes_ ) external { uint256 indexesLength = indexes_.length; uint256 index; for (uint256 i = 0; i < indexesLength; ) { index = indexes_[i]; allowances_[index] = 0; unchecked { ++i; } } emit RevokeLPAllowance( msg.sender, spender_, indexes_ ); } /** * @notice See `IPoolLPActions` for descriptions * @dev === Write state === * @dev `approvedTransferors` mapping * @dev === Emit events === * @dev - `ApproveLPTransferors` */ function approveLPTransferors( mapping(address => bool) storage allowances_, address[] calldata transferors_ ) external { uint256 transferorsLength = transferors_.length; for (uint256 i = 0; i < transferorsLength; ) { allowances_[transferors_[i]] = true; unchecked { ++i; } } emit ApproveLPTransferors( msg.sender, transferors_ ); } /** * @notice See `IPoolLPActions` for descriptions * @dev === Write state === * @dev `approvedTransferors` mapping * @dev === Emit events === * @dev - `RevokeLPTransferors` */ function revokeLPTransferors( mapping(address => bool) storage allowances_, address[] calldata transferors_ ) external { uint256 transferorsLength = transferors_.length; for (uint256 i = 0; i < transferorsLength; ) { delete allowances_[transferors_[i]]; unchecked { ++i; } } emit RevokeLPTransferors( msg.sender, transferors_ ); } /** * @notice See `IPoolLPActions` for descriptions * @dev === Write state === * @dev delete allowance mapping * @dev increment new `lender.lps` accumulator and `lender.depositTime` state * @dev delete old lender from `bucket -> lender` mapping * @dev === Reverts on === * @dev invalid index `InvalidIndex()` * @dev no allowance `NoAllowance()` * @dev === Emit events === * @dev - `TransferLP` */ function transferLP( mapping(uint256 => Bucket) storage buckets_, mapping(address => mapping(address => mapping(uint256 => uint256))) storage allowances_, mapping(address => mapping(address => bool)) storage approvedTransferors_, address ownerAddress_, address newOwnerAddress_, uint256[] calldata indexes_ ) external { // revert if msg.sender is not the new owner and is not approved as a transferor by the new owner if (newOwnerAddress_ != msg.sender && !approvedTransferors_[newOwnerAddress_][msg.sender]) revert TransferorNotApproved(); // revert if new owner address is the same as old owner address if (ownerAddress_ == newOwnerAddress_) revert TransferToSameOwner(); uint256 indexesLength = indexes_.length; uint256 index; uint256 lpTransferred; for (uint256 i = 0; i < indexesLength; ) { index = indexes_[i]; // revert if invalid index if (index > MAX_FENWICK_INDEX) revert InvalidIndex(); Bucket storage bucket = buckets_[index]; Lender storage owner = bucket.lenders[ownerAddress_]; uint256 bankruptcyTime = bucket.bankruptcyTime; uint256 ownerDepositTime = owner.depositTime; uint256 ownerLpBalance = bankruptcyTime < ownerDepositTime ? owner.lps : 0; uint256 allowedAmount = allowances_[ownerAddress_][newOwnerAddress_][index]; if (allowedAmount == 0) revert NoAllowance(); // transfer allowed amount or entire LP balance allowedAmount = Maths.min(allowedAmount, ownerLpBalance); // move owner LP (if any) to the new owner if (allowedAmount != 0) { Lender storage newOwner = bucket.lenders[newOwnerAddress_]; uint256 newOwnerDepositTime = newOwner.depositTime; if (newOwnerDepositTime > bankruptcyTime) { // deposit happened in a healthy bucket, add amount of LP to new owner newOwner.lps += allowedAmount; } else { // bucket bankruptcy happened after deposit, reset balance and add amount of LP to new owner newOwner.lps = allowedAmount; } // remove amount of LP from old owner owner.lps -= allowedAmount; // add amount of LP to total LP transferred lpTransferred += allowedAmount; // set the deposit time as the max of transferred deposit and current deposit time newOwner.depositTime = Maths.max(ownerDepositTime, newOwnerDepositTime); } // reset allowances of transferred LP delete allowances_[ownerAddress_][newOwnerAddress_][index]; unchecked { ++i; } } emit TransferLP( ownerAddress_, newOwnerAddress_, indexes_, lpTransferred ); } }
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.18; import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { PoolType } from '../../interfaces/pool/IPool.sol'; import { AuctionsState, Borrower, Bucket, DepositsState, Kicker, Lender, Liquidation, LoansState, PoolState, ReserveAuctionState } from '../../interfaces/pool/commons/IPoolState.sol'; import { KickResult, KickReserveAuctionParams } from '../../interfaces/pool/commons/IPoolInternals.sol'; import { MAX_INFLATED_PRICE, COLLATERALIZATION_FACTOR, _bondParams, _borrowFeeRate, _claimableReserves, _htp, _isCollateralized, _priceAt, _reserveAuctionPrice } from '../helpers/PoolHelper.sol'; import { _revertIfPriceDroppedBelowLimit } from '../helpers/RevertsHelper.sol'; import { Buckets } from '../internal/Buckets.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Loans } from '../internal/Loans.sol'; import { Maths } from '../internal/Maths.sol'; /** @title Auctions kicker actions library @notice External library containing kicker actions involving auctions within pool: - kick undercollateralized loans; start reserve auctions */ library KickerActions { /*************************/ /*** Local Var Structs ***/ /*************************/ /// @dev Struct used for `kick` function local vars. struct KickLocalVars { uint256 borrowerDebt; // [WAD] the accrued debt of kicked borrower uint256 borrowerCollateral; // [WAD] amount of kicked borrower collateral uint256 t0ReserveSettleAmount; // [WAD] Amount of t0Debt that could be settled via reserves in an auction uint256 borrowerNpTpRatio; // [WAD] borrower NP to TP ratio uint256 neutralPrice; // [WAD] neutral price recorded in kick action uint256 htp; // [WAD] highest threshold price (including the collateralization factor) in pool uint256 referencePrice; // [WAD] used to calculate auction start price uint256 bondFactor; // [WAD] bond factor of kicked auction uint256 bondSize; // [WAD] bond size of kicked auction uint256 debtToCollateral; // [WAD] borrower debt to collateral at kick time } /// @dev Struct used for `lenderKick` function local vars. struct LenderKickLocalVars { uint256 bucketDeposit; // [WAD] amount of quote tokens in bucket uint256 bucketPrice; // [WAD] bucket price uint256 entitledAmount; // [WAD] amount that lender is entitled to remove at specified index uint256 lenderLP; // [WAD] LP of lender in bucket } /**************/ /*** Events ***/ /**************/ // See `IPoolEvents` for descriptions event BondWithdrawn(address indexed kicker, address indexed receiver, uint256 amount); event BucketBankruptcy(uint256 indexed index, uint256 lpForfeited); event Kick(address indexed borrower, uint256 debt, uint256 collateral, uint256 bond); event KickReserveAuction(uint256 claimableReservesRemaining, uint256 auctionPrice, uint256 currentBurnEpoch); event RemoveQuoteToken(address indexed lender, uint256 indexed price, uint256 amount, uint256 lpRedeemed, uint256 lup); /**************/ /*** Errors ***/ /**************/ // See `IPoolErrors` for descriptions error AuctionActive(); error BorrowerOk(); error InsufficientLiquidity(); error NoReserves(); error PriceBelowLUP(); error ReserveAuctionTooSoon(); /***************************/ /*** External Functions ***/ /***************************/ /** * @notice See `IPoolKickerActions` for descriptions. * @return kickResult_ The `KickResult` struct result of the kick action. */ function kick( AuctionsState storage auctions_, DepositsState storage deposits_, LoansState storage loans_, PoolState calldata poolState_, address borrowerAddress_, uint256 limitIndex_ ) external returns ( KickResult memory kickResult_ ) { uint256 curLup = Deposits.getLup(deposits_, poolState_.debt); kickResult_ = _kick( auctions_, loans_, poolState_, borrowerAddress_, limitIndex_, curLup // proposed LUP is the current pool LUP ); // return current LUP in pool kickResult_.lup = curLup; } /** * @notice See `IPoolKickerActions` for descriptions. * @dev === Reverts on === * @dev bucket price below current pool `LUP` `PriceBelowLUP()` * @dev insufficient deposit to kick auction `InsufficientLiquidity()` * @return kickResult_ The `KickResult` struct result of the kick action. */ function lenderKick( AuctionsState storage auctions_, DepositsState storage deposits_, mapping(uint256 => Bucket) storage buckets_, LoansState storage loans_, PoolState calldata poolState_, uint256 index_, uint256 limitIndex_ ) external returns ( KickResult memory kickResult_ ) { LenderKickLocalVars memory vars; vars.bucketPrice = _priceAt(index_); // revert if the bucket price is below current LUP uint256 curLup = Deposits.getLup(deposits_, poolState_.debt); if (vars.bucketPrice < curLup) revert PriceBelowLUP(); Bucket storage bucket = buckets_[index_]; Lender storage lender = bucket.lenders[msg.sender]; vars.lenderLP = bucket.bankruptcyTime < lender.depositTime ? lender.lps : 0; vars.bucketDeposit = Deposits.valueAt(deposits_, index_); // calculate amount lender is entitled in current bucket (based on lender LP in bucket) vars.entitledAmount = Buckets.lpToQuoteTokens( bucket.collateral, bucket.lps, vars.bucketDeposit, vars.lenderLP, vars.bucketPrice, Math.Rounding.Down ); // cap the amount entitled at bucket deposit if (vars.entitledAmount > vars.bucketDeposit) vars.entitledAmount = vars.bucketDeposit; // revert if no entitled amount if (vars.entitledAmount == 0) revert InsufficientLiquidity(); // add amount to remove to pool debt in order to calculate proposed LUP // this simulates LUP movement with additional debt uint256 proposedLup = Deposits.getLup(deposits_, poolState_.debt + vars.entitledAmount); // kick top borrower kickResult_ = _kick( auctions_, loans_, poolState_, Loans.getMax(loans_).borrower, limitIndex_, proposedLup ); // return current LUP in pool kickResult_.lup = curLup; } /*************************/ /*** Reserve Auction ***/ /*************************/ /** * @notice See `IPoolKickerActions` for descriptions. * @dev === Write state === * @dev update `reserveAuction.unclaimed` accumulator * @dev update `reserveAuction.kicked` timestamp state * @dev === Reverts on === * @dev no reserves to claim `NoReserves()` * @dev 5 days not passed `ReserveAuctionTooSoon()` * @dev === Emit events === * @dev - `KickReserveAuction` */ function kickReserveAuction( AuctionsState storage auctions_, ReserveAuctionState storage reserveAuction_, KickReserveAuctionParams calldata params_ ) external { // retrieve timestamp of latest burn event and last burn timestamp uint256 latestBurnEpoch = reserveAuction_.latestBurnEventEpoch; // check that at least five days have passed since the last reserve auction was kicked if (block.timestamp < reserveAuction_.kicked + 120 hours) { revert ReserveAuctionTooSoon(); } uint256 curUnclaimedAuctionReserve = reserveAuction_.unclaimed; uint256 claimable = _claimableReserves( Maths.wmul(params_.t0PoolDebt, params_.inflator), params_.poolSize, auctions_.totalBondEscrowed, curUnclaimedAuctionReserve, params_.poolBalance ); curUnclaimedAuctionReserve += claimable; if (curUnclaimedAuctionReserve == 0) revert NoReserves(); reserveAuction_.unclaimed = curUnclaimedAuctionReserve; reserveAuction_.kicked = block.timestamp; reserveAuction_.lastKickedReserves = curUnclaimedAuctionReserve; // increment latest burn event epoch and update burn event timestamp latestBurnEpoch += 1; reserveAuction_.latestBurnEventEpoch = latestBurnEpoch; reserveAuction_.burnEvents[latestBurnEpoch].timestamp = block.timestamp; emit KickReserveAuction( curUnclaimedAuctionReserve, _reserveAuctionPrice(block.timestamp, curUnclaimedAuctionReserve), latestBurnEpoch ); } function withdrawBonds( AuctionsState storage auctions_, address recipient_, uint256 maxAmount_ ) external returns (uint256 amount_) { uint256 claimable = auctions_.kickers[msg.sender].claimable; // the amount to claim is constrained by the claimable balance of sender // claiming escrowed bonds is not constraiend by the pool balance amount_ = Maths.min(maxAmount_, claimable); // revert if no amount to claim if (amount_ == 0) revert InsufficientLiquidity(); // decrement total bond escrowed auctions_.totalBondEscrowed -= amount_; auctions_.kickers[msg.sender].claimable -= amount_; emit BondWithdrawn(msg.sender, recipient_, amount_); } /***************************/ /*** Internal Functions ***/ /***************************/ /** * @notice Called to start borrower liquidation and to update the auctions queue. * @dev === Write state === * @dev - `_recordAuction`: * @dev `borrower -> liquidation` mapping update * @dev increment `auctions count` accumulator * @dev increment `auctions.totalBondEscrowed` accumulator * @dev updates auction queue state * @dev - `_updateEscrowedBonds`: * @dev update `locked` and `claimable` kicker accumulators * @dev - `Loans.remove`: * @dev delete borrower from `indices => borrower` address mapping * @dev remove loan from loans array * @dev === Emit events === * @dev - `Kick` * @param auctions_ Struct for pool auctions state. * @param loans_ Struct for pool loans state. * @param poolState_ Current state of the pool. * @param borrowerAddress_ Address of the borrower to kick. * @param limitIndex_ Index of the lower bound of `NP` tolerated when kicking the auction. * @param proposedLup_ Proposed `LUP` in pool. * @return kickResult_ The `KickResult` struct result of the kick action. */ function _kick( AuctionsState storage auctions_, LoansState storage loans_, PoolState calldata poolState_, address borrowerAddress_, uint256 limitIndex_, uint256 proposedLup_ ) internal returns ( KickResult memory kickResult_ ) { Liquidation storage liquidation = auctions_.liquidations[borrowerAddress_]; // revert if liquidation is active if (liquidation.kickTime != 0) revert AuctionActive(); Borrower storage borrower = loans_.borrowers[borrowerAddress_]; kickResult_.t0KickedDebt = borrower.t0Debt; kickResult_.collateralPreAction = borrower.collateral; KickLocalVars memory vars; vars.borrowerDebt = Maths.wmul(kickResult_.t0KickedDebt, poolState_.inflator); vars.borrowerCollateral = kickResult_.collateralPreAction; vars.t0ReserveSettleAmount = Maths.wmul(kickResult_.t0KickedDebt, _borrowFeeRate(poolState_.rate)) / 2; vars.borrowerNpTpRatio = borrower.npTpRatio; // revert if kick on a collateralized borrower if (_isCollateralized(vars.borrowerDebt, vars.borrowerCollateral, proposedLup_, poolState_.poolType)) { revert BorrowerOk(); } // calculate auction params // neutral price = Tp * Np to Tp ratio // neutral price is capped at 50 * max pool price vars.neutralPrice = Maths.min( Math.mulDiv(Maths.wmul(vars.borrowerDebt, COLLATERALIZATION_FACTOR), vars.borrowerNpTpRatio, vars.borrowerCollateral), MAX_INFLATED_PRICE ); // check if NP is not less than price at the limit index provided by the kicker - done to prevent frontrunning kick auction call with a large amount of loan // which will make it harder for kicker to earn a reward and more likely that the kicker is penalized _revertIfPriceDroppedBelowLimit(vars.neutralPrice, limitIndex_); vars.htp = _htp(Loans.getMax(loans_).t0DebtToCollateral, poolState_.inflator); vars.referencePrice = Maths.min(Maths.max(vars.htp, vars.neutralPrice), MAX_INFLATED_PRICE); (vars.bondFactor, vars.bondSize) = _bondParams( vars.borrowerDebt, vars.borrowerNpTpRatio ); vars.debtToCollateral = Maths.wdiv(vars.borrowerDebt, vars.borrowerCollateral); // record liquidation info _recordAuction( auctions_, liquidation, borrowerAddress_, vars.bondSize, vars.bondFactor, vars.referencePrice, vars.neutralPrice, vars.debtToCollateral, vars.t0ReserveSettleAmount ); // update escrowed bonds balances and get the difference needed to cover bond (after using any kick claimable funds if any) kickResult_.amountToCoverBond = _updateEscrowedBonds(auctions_, vars.bondSize); // remove kicked loan from heap Loans.remove(loans_, borrowerAddress_, loans_.indices[borrowerAddress_]); emit Kick( borrowerAddress_, vars.borrowerDebt, vars.borrowerCollateral, vars.bondSize ); } /** * @notice Updates escrowed bonds balances, reuse kicker claimable funds and calculates difference needed to cover new bond. * @dev === Write state === * @dev update `locked` and `claimable` kicker accumulators * @dev update `totalBondEscrowed` accumulator * @param auctions_ Struct for pool auctions state. * @param bondSize_ Bond size to cover newly kicked auction. * @return bondDifference_ The amount that kicker should send to pool to cover auction bond. */ function _updateEscrowedBonds( AuctionsState storage auctions_, uint256 bondSize_ ) internal returns (uint256 bondDifference_){ Kicker storage kicker = auctions_.kickers[msg.sender]; kicker.locked += bondSize_; uint256 kickerClaimable = kicker.claimable; if (kickerClaimable >= bondSize_) { // no need to update total bond escrowed as bond is covered by kicker claimable (which is already tracked by accumulator) kicker.claimable -= bondSize_; } else { bondDifference_ = bondSize_ - kickerClaimable; kicker.claimable = 0; // increment total bond escrowed by amount needed to cover bond difference auctions_.totalBondEscrowed += bondDifference_; } } /** * @notice Saves in storage a new liquidation that was kicked. * @dev === Write state === * @dev `borrower -> liquidation` mapping update * @dev increment auctions count accumulator * @dev updates auction queue state * @param auctions_ Struct for pool auctions state. * @param liquidation_ Struct for current auction state. * @param borrowerAddress_ Address of the borrower that is kicked. * @param bondSize_ Bond size to cover newly kicked auction. * @param bondFactor_ Bond factor of the newly kicked auction. * @param referencePrice_ Used to calculate auction start price. * @param neutralPrice_ Current pool `Neutral Price`. * @param debtToCollateral_ Borrower debt to collateral at time of kick. * @param t0ReserveSettleAmount_ Amount of t0Debt that could be settled via reserves in auction */ function _recordAuction( AuctionsState storage auctions_, Liquidation storage liquidation_, address borrowerAddress_, uint256 bondSize_, uint256 bondFactor_, uint256 referencePrice_, uint256 neutralPrice_, uint256 debtToCollateral_, uint256 t0ReserveSettleAmount_ ) internal { // record liquidation info liquidation_.kicker = msg.sender; liquidation_.kickTime = uint96(block.timestamp); liquidation_.bondSize = SafeCast.toUint160(bondSize_); liquidation_.bondFactor = SafeCast.toUint96(bondFactor_); liquidation_.neutralPrice = SafeCast.toUint96(neutralPrice_); liquidation_.debtToCollateral = debtToCollateral_; liquidation_.t0ReserveSettleAmount = t0ReserveSettleAmount_; // increment number of active auctions ++auctions_.noOfAuctions; // update auctions queue if (auctions_.head != address(0)) { // other auctions in queue, liquidation doesn't exist or overwriting. address tail = auctions_.tail; auctions_.liquidations[tail].next = borrowerAddress_; liquidation_.prev = tail; liquidation_.referencePrice = SafeCast.toUint96(Maths.max(referencePrice_, auctions_.liquidations[tail].referencePrice)); } else { // first auction in queue auctions_.head = borrowerAddress_; liquidation_.referencePrice = SafeCast.toUint96(referencePrice_); } // update liquidation with the new ordering auctions_.tail = borrowerAddress_; } }
// SPDX-License-Identifier: Unlicense pragma solidity >=0.8.4; import "./PRBMath.sol"; /// @title PRBMathUD60x18 /// @author Paul Razvan Berg /// @notice Smart contract library for advanced fixed-point math that works with uint256 numbers considered to have 18 /// trailing decimals. We call this number representation unsigned 60.18-decimal fixed-point, since there can be up to 60 /// digits in the integer part and up to 18 decimals in the fractional part. The numbers are bound by the minimum and the /// maximum values permitted by the Solidity type uint256. library PRBMathUD60x18 { /// @dev Half the SCALE number. uint256 internal constant HALF_SCALE = 5e17; /// @dev log2(e) as an unsigned 60.18-decimal fixed-point number. uint256 internal constant LOG2_E = 1_442695040888963407; /// @dev The maximum value an unsigned 60.18-decimal fixed-point number can have. uint256 internal constant MAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935; /// @dev The maximum whole value an unsigned 60.18-decimal fixed-point number can have. uint256 internal constant MAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000; /// @dev How many trailing decimals can be represented. uint256 internal constant SCALE = 1e18; /// @notice Calculates the arithmetic average of x and y, rounding down. /// @param x The first operand as an unsigned 60.18-decimal fixed-point number. /// @param y The second operand as an unsigned 60.18-decimal fixed-point number. /// @return result The arithmetic average as an unsigned 60.18-decimal fixed-point number. function avg(uint256 x, uint256 y) internal pure returns (uint256 result) { // The operations can never overflow. unchecked { // The last operand checks if both x and y are odd and if that is the case, we add 1 to the result. We need // to do this because if both numbers are odd, the 0.5 remainder gets truncated twice. result = (x >> 1) + (y >> 1) + (x & y & 1); } } /// @notice Yields the least unsigned 60.18 decimal fixed-point number greater than or equal to x. /// /// @dev 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 must be less than or equal to MAX_WHOLE_UD60x18. /// /// @param x The unsigned 60.18-decimal fixed-point number to ceil. /// @param result The least integer greater than or equal to x, as an unsigned 60.18-decimal fixed-point number. function ceil(uint256 x) internal pure returns (uint256 result) { if (x > MAX_WHOLE_UD60x18) { revert PRBMathUD60x18__CeilOverflow(x); } assembly { // Equivalent to "x % SCALE" but faster. let remainder := mod(x, SCALE) // Equivalent to "SCALE - remainder" but faster. let delta := sub(SCALE, remainder) // Equivalent to "x + delta * (remainder > 0 ? 1 : 0)" but faster. result := add(x, mul(delta, gt(remainder, 0))) } } /// @notice Divides two unsigned 60.18-decimal fixed-point numbers, returning a new unsigned 60.18-decimal fixed-point number. /// /// @dev Uses mulDiv to enable overflow-safe multiplication and division. /// /// Requirements: /// - The denominator cannot be zero. /// /// @param x The numerator as an unsigned 60.18-decimal fixed-point number. /// @param y The denominator as an unsigned 60.18-decimal fixed-point number. /// @param result The quotient as an unsigned 60.18-decimal fixed-point number. function div(uint256 x, uint256 y) internal pure returns (uint256 result) { result = PRBMath.mulDiv(x, SCALE, y); } /// @notice Returns Euler's number as an unsigned 60.18-decimal fixed-point number. /// @dev See https://en.wikipedia.org/wiki/E_(mathematical_constant). function e() internal pure returns (uint256 result) { result = 2_718281828459045235; } /// @notice Calculates the natural exponent of x. /// /// @dev Based on the insight that e^x = 2^(x * log2(e)). /// /// Requirements: /// - All from "log2". /// - x must be less than 133.084258667509499441. /// /// @param x The exponent as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function exp(uint256 x) internal pure returns (uint256 result) { // Without this check, the value passed to "exp2" would be greater than 192. if (x >= 133_084258667509499441) { revert PRBMathUD60x18__ExpInputTooBig(x); } // Do the fixed-point multiplication inline to save gas. unchecked { uint256 doubleScaleProduct = x * LOG2_E; result = exp2((doubleScaleProduct + HALF_SCALE) / SCALE); } } /// @notice Calculates the binary exponent of x using the binary fraction method. /// /// @dev See https://ethereum.stackexchange.com/q/79903/24693. /// /// Requirements: /// - x must be 192 or less. /// - The result must fit within MAX_UD60x18. /// /// @param x The exponent as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function exp2(uint256 x) internal pure returns (uint256 result) { // 2^192 doesn't fit within the 192.64-bit format used internally in this function. if (x >= 192e18) { revert PRBMathUD60x18__Exp2InputTooBig(x); } unchecked { // Convert x to the 192.64-bit fixed-point format. uint256 x192x64 = (x << 64) / SCALE; // Pass x to the PRBMath.exp2 function, which uses the 192.64-bit fixed-point number representation. result = PRBMath.exp2(x192x64); } } /// @notice Yields the greatest unsigned 60.18 decimal fixed-point number less than or equal to x. /// @dev 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. /// @param x The unsigned 60.18-decimal fixed-point number to floor. /// @param result The greatest integer less than or equal to x, as an unsigned 60.18-decimal fixed-point number. function floor(uint256 x) internal pure returns (uint256 result) { assembly { // Equivalent to "x % SCALE" but faster. let remainder := mod(x, SCALE) // Equivalent to "x - remainder * (remainder > 0 ? 1 : 0)" but faster. result := sub(x, mul(remainder, gt(remainder, 0))) } } /// @notice Yields the excess beyond the floor of x. /// @dev Based on the odd function definition https://en.wikipedia.org/wiki/Fractional_part. /// @param x The unsigned 60.18-decimal fixed-point number to get the fractional part of. /// @param result The fractional part of x as an unsigned 60.18-decimal fixed-point number. function frac(uint256 x) internal pure returns (uint256 result) { assembly { result := mod(x, SCALE) } } /// @notice Converts a number from basic integer form to unsigned 60.18-decimal fixed-point representation. /// /// @dev Requirements: /// - x must be less than or equal to MAX_UD60x18 divided by SCALE. /// /// @param x The basic integer to convert. /// @param result The same number in unsigned 60.18-decimal fixed-point representation. function fromUint(uint256 x) internal pure returns (uint256 result) { unchecked { if (x > MAX_UD60x18 / SCALE) { revert PRBMathUD60x18__FromUintOverflow(x); } result = x * SCALE; } } /// @notice Calculates geometric mean of x and y, i.e. sqrt(x * y), rounding down. /// /// @dev Requirements: /// - x * y must fit within MAX_UD60x18, lest it overflows. /// /// @param x The first operand as an unsigned 60.18-decimal fixed-point number. /// @param y The second operand as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function gm(uint256 x, uint256 y) internal pure returns (uint256 result) { if (x == 0) { return 0; } unchecked { // Checking for overflow this way is faster than letting Solidity do it. uint256 xy = x * y; if (xy / x != y) { revert PRBMathUD60x18__GmOverflow(x, y); } // We don't need to multiply by the SCALE here because the x*y product had already picked up a factor of SCALE // during multiplication. See the comments within the "sqrt" function. result = PRBMath.sqrt(xy); } } /// @notice Calculates 1 / x, rounding toward zero. /// /// @dev Requirements: /// - x cannot be zero. /// /// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the inverse. /// @return result The inverse as an unsigned 60.18-decimal fixed-point number. function inv(uint256 x) internal pure returns (uint256 result) { unchecked { // 1e36 is SCALE * SCALE. result = 1e36 / x; } } /// @notice Calculates the natural logarithm of x. /// /// @dev Based on the insight that ln(x) = log2(x) / log2(e). /// /// Requirements: /// - All from "log2". /// /// Caveats: /// - All from "log2". /// - This doesn't return exactly 1 for 2.718281828459045235, for that we would need more fine-grained precision. /// /// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the natural logarithm. /// @return result The natural logarithm as an unsigned 60.18-decimal fixed-point number. function ln(uint256 x) internal pure returns (uint256 result) { // Do the fixed-point multiplication inline to save gas. This is overflow-safe because the maximum value that log2(x) // can return is 196205294292027477728. unchecked { result = (log2(x) * SCALE) / LOG2_E; } } /// @notice Calculates the common logarithm of x. /// /// @dev First checks if x is an exact power of ten and it stops if yes. If it's not, calculates the common /// logarithm based on the insight that log10(x) = log2(x) / log2(10). /// /// Requirements: /// - All from "log2". /// /// Caveats: /// - All from "log2". /// /// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the common logarithm. /// @return result The common logarithm as an unsigned 60.18-decimal fixed-point number. function log10(uint256 x) internal pure returns (uint256 result) { if (x < SCALE) { revert PRBMathUD60x18__LogInputTooSmall(x); } // Note that the "mul" in this block is the assembly multiplication operation, not the "mul" function defined // in this contract. // prettier-ignore assembly { switch x case 1 { result := mul(SCALE, sub(0, 18)) } case 10 { result := mul(SCALE, sub(1, 18)) } case 100 { result := mul(SCALE, sub(2, 18)) } case 1000 { result := mul(SCALE, sub(3, 18)) } case 10000 { result := mul(SCALE, sub(4, 18)) } case 100000 { result := mul(SCALE, sub(5, 18)) } case 1000000 { result := mul(SCALE, sub(6, 18)) } case 10000000 { result := mul(SCALE, sub(7, 18)) } case 100000000 { result := mul(SCALE, sub(8, 18)) } case 1000000000 { result := mul(SCALE, sub(9, 18)) } case 10000000000 { result := mul(SCALE, sub(10, 18)) } case 100000000000 { result := mul(SCALE, sub(11, 18)) } case 1000000000000 { result := mul(SCALE, sub(12, 18)) } case 10000000000000 { result := mul(SCALE, sub(13, 18)) } case 100000000000000 { result := mul(SCALE, sub(14, 18)) } case 1000000000000000 { result := mul(SCALE, sub(15, 18)) } case 10000000000000000 { result := mul(SCALE, sub(16, 18)) } case 100000000000000000 { result := mul(SCALE, sub(17, 18)) } case 1000000000000000000 { result := 0 } case 10000000000000000000 { result := SCALE } case 100000000000000000000 { result := mul(SCALE, 2) } case 1000000000000000000000 { result := mul(SCALE, 3) } case 10000000000000000000000 { result := mul(SCALE, 4) } case 100000000000000000000000 { result := mul(SCALE, 5) } case 1000000000000000000000000 { result := mul(SCALE, 6) } case 10000000000000000000000000 { result := mul(SCALE, 7) } case 100000000000000000000000000 { result := mul(SCALE, 8) } case 1000000000000000000000000000 { result := mul(SCALE, 9) } case 10000000000000000000000000000 { result := mul(SCALE, 10) } case 100000000000000000000000000000 { result := mul(SCALE, 11) } case 1000000000000000000000000000000 { result := mul(SCALE, 12) } case 10000000000000000000000000000000 { result := mul(SCALE, 13) } case 100000000000000000000000000000000 { result := mul(SCALE, 14) } case 1000000000000000000000000000000000 { result := mul(SCALE, 15) } case 10000000000000000000000000000000000 { result := mul(SCALE, 16) } case 100000000000000000000000000000000000 { result := mul(SCALE, 17) } case 1000000000000000000000000000000000000 { result := mul(SCALE, 18) } case 10000000000000000000000000000000000000 { result := mul(SCALE, 19) } case 100000000000000000000000000000000000000 { result := mul(SCALE, 20) } case 1000000000000000000000000000000000000000 { result := mul(SCALE, 21) } case 10000000000000000000000000000000000000000 { result := mul(SCALE, 22) } case 100000000000000000000000000000000000000000 { result := mul(SCALE, 23) } case 1000000000000000000000000000000000000000000 { result := mul(SCALE, 24) } case 10000000000000000000000000000000000000000000 { result := mul(SCALE, 25) } case 100000000000000000000000000000000000000000000 { result := mul(SCALE, 26) } case 1000000000000000000000000000000000000000000000 { result := mul(SCALE, 27) } case 10000000000000000000000000000000000000000000000 { result := mul(SCALE, 28) } case 100000000000000000000000000000000000000000000000 { result := mul(SCALE, 29) } case 1000000000000000000000000000000000000000000000000 { result := mul(SCALE, 30) } case 10000000000000000000000000000000000000000000000000 { result := mul(SCALE, 31) } case 100000000000000000000000000000000000000000000000000 { result := mul(SCALE, 32) } case 1000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 33) } case 10000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 34) } case 100000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 35) } case 1000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 36) } case 10000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 37) } case 100000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 38) } case 1000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 39) } case 10000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 40) } case 100000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 41) } case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 42) } case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 43) } case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 44) } case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 45) } case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 46) } case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 47) } case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 48) } case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 49) } case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 50) } case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 51) } case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 52) } case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 53) } case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 54) } case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 55) } case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 56) } case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 57) } case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 58) } case 100000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(SCALE, 59) } default { result := MAX_UD60x18 } } if (result == MAX_UD60x18) { // Do the fixed-point division inline to save gas. The denominator is log2(10). unchecked { result = (log2(x) * SCALE) / 3_321928094887362347; } } } /// @notice Calculates the binary logarithm of x. /// /// @dev Based on the iterative approximation algorithm. /// https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation /// /// Requirements: /// - x must be greater than or equal to SCALE, otherwise the result would be negative. /// /// Caveats: /// - The results are nor perfectly accurate to the last decimal, due to the lossy precision of the iterative approximation. /// /// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the binary logarithm. /// @return result The binary logarithm as an unsigned 60.18-decimal fixed-point number. function log2(uint256 x) internal pure returns (uint256 result) { if (x < SCALE) { revert PRBMathUD60x18__LogInputTooSmall(x); } unchecked { // Calculate the integer part of the logarithm and add it to the result and finally calculate y = x * 2^(-n). uint256 n = PRBMath.mostSignificantBit(x / SCALE); // The integer part of the logarithm as an unsigned 60.18-decimal fixed-point number. The operation can't overflow // because n is maximum 255 and SCALE is 1e18. result = n * SCALE; // This is y = x * 2^(-n). uint256 y = x >> n; // If y = 1, the fractional part is zero. if (y == SCALE) { return result; } // Calculate the fractional part via the iterative approximation. // The "delta >>= 1" part is equivalent to "delta /= 2", but shifting bits is faster. for (uint256 delta = HALF_SCALE; delta > 0; delta >>= 1) { y = (y * y) / SCALE; // Is y^2 > 2 and so in the range [2,4)? if (y >= 2 * SCALE) { // Add the 2^(-m) factor to the logarithm. result += delta; // Corresponds to z/2 on Wikipedia. y >>= 1; } } } } /// @notice Multiplies two unsigned 60.18-decimal fixed-point numbers together, returning a new unsigned 60.18-decimal /// fixed-point number. /// @dev See the documentation for the "PRBMath.mulDivFixedPoint" function. /// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number. /// @param y The multiplier as an unsigned 60.18-decimal fixed-point number. /// @return result The product as an unsigned 60.18-decimal fixed-point number. function mul(uint256 x, uint256 y) internal pure returns (uint256 result) { result = PRBMath.mulDivFixedPoint(x, y); } /// @notice Returns PI as an unsigned 60.18-decimal fixed-point number. function pi() internal pure returns (uint256 result) { result = 3_141592653589793238; } /// @notice Raises x to the power of y. /// /// @dev Based on the insight that x^y = 2^(log2(x) * y). /// /// Requirements: /// - All from "exp2", "log2" and "mul". /// /// Caveats: /// - All from "exp2", "log2" and "mul". /// - Assumes 0^0 is 1. /// /// @param x Number to raise to given power y, as an unsigned 60.18-decimal fixed-point number. /// @param y Exponent to raise x to, as an unsigned 60.18-decimal fixed-point number. /// @return result x raised to power y, as an unsigned 60.18-decimal fixed-point number. function pow(uint256 x, uint256 y) internal pure returns (uint256 result) { if (x == 0) { result = y == 0 ? SCALE : uint256(0); } else { result = exp2(mul(log2(x), y)); } } /// @notice Raises x (unsigned 60.18-decimal fixed-point number) to the power of y (basic unsigned integer) using the /// famous algorithm "exponentiation by squaring". /// /// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring /// /// Requirements: /// - The result must fit within MAX_UD60x18. /// /// Caveats: /// - All from "mul". /// - Assumes 0^0 is 1. /// /// @param x The base as an unsigned 60.18-decimal fixed-point number. /// @param y The exponent as an uint256. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function powu(uint256 x, uint256 y) internal pure returns (uint256 result) { // Calculate the first iteration of the loop in advance. result = y & 1 > 0 ? x : SCALE; // Equivalent to "for(y /= 2; y > 0; y /= 2)" but faster. for (y >>= 1; y > 0; y >>= 1) { x = PRBMath.mulDivFixedPoint(x, x); // Equivalent to "y % 2 == 1" but faster. if (y & 1 > 0) { result = PRBMath.mulDivFixedPoint(result, x); } } } /// @notice Returns 1 as an unsigned 60.18-decimal fixed-point number. function scale() internal pure returns (uint256 result) { result = SCALE; } /// @notice Calculates the square root of x, rounding down. /// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method. /// /// Requirements: /// - x must be less than MAX_UD60x18 / SCALE. /// /// @param x The unsigned 60.18-decimal fixed-point number for which to calculate the square root. /// @return result The result as an unsigned 60.18-decimal fixed-point . function sqrt(uint256 x) internal pure returns (uint256 result) { unchecked { if (x > MAX_UD60x18 / SCALE) { revert PRBMathUD60x18__SqrtOverflow(x); } // Multiply x by the SCALE to account for the factor of SCALE that is picked up when multiplying two unsigned // 60.18-decimal fixed-point numbers together (in this case, those two numbers are both the square root). result = PRBMath.sqrt(x * SCALE); } } /// @notice Converts a unsigned 60.18-decimal fixed-point number to basic integer form, rounding down in the process. /// @param x The unsigned 60.18-decimal fixed-point number to convert. /// @return result The same number in basic integer form. function toUint(uint256 x) internal pure returns (uint256 result) { unchecked { result = x / SCALE; } } }
// SPDX-License-Identifier: Unlicense pragma solidity >=0.8.4; /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivFixedPointOverflow(uint256 prod1); /// @notice Emitted when the result overflows uint256. error PRBMath__MulDivOverflow(uint256 prod1, uint256 denominator); /// @notice Emitted when one of the inputs is type(int256).min. error PRBMath__MulDivSignedInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows int256. error PRBMath__MulDivSignedOverflow(uint256 rAbs); /// @notice Emitted when the input is MIN_SD59x18. error PRBMathSD59x18__AbsInputTooSmall(); /// @notice Emitted when ceiling a number overflows SD59x18. error PRBMathSD59x18__CeilOverflow(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__DivInputTooSmall(); /// @notice Emitted when one of the intermediary unsigned results overflows SD59x18. error PRBMathSD59x18__DivOverflow(uint256 rAbs); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathSD59x18__ExpInputTooBig(int256 x); /// @notice Emitted when the input is greater than 192. error PRBMathSD59x18__Exp2InputTooBig(int256 x); /// @notice Emitted when flooring a number underflows SD59x18. error PRBMathSD59x18__FloorUnderflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format overflows SD59x18. error PRBMathSD59x18__FromIntOverflow(int256 x); /// @notice Emitted when converting a basic integer to the fixed-point format underflows SD59x18. error PRBMathSD59x18__FromIntUnderflow(int256 x); /// @notice Emitted when the product of the inputs is negative. error PRBMathSD59x18__GmNegativeProduct(int256 x, int256 y); /// @notice Emitted when multiplying the inputs overflows SD59x18. error PRBMathSD59x18__GmOverflow(int256 x, int256 y); /// @notice Emitted when the input is less than or equal to zero. error PRBMathSD59x18__LogInputTooSmall(int256 x); /// @notice Emitted when one of the inputs is MIN_SD59x18. error PRBMathSD59x18__MulInputTooSmall(); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__MulOverflow(uint256 rAbs); /// @notice Emitted when the intermediary absolute result overflows SD59x18. error PRBMathSD59x18__PowuOverflow(uint256 rAbs); /// @notice Emitted when the input is negative. error PRBMathSD59x18__SqrtNegativeInput(int256 x); /// @notice Emitted when the calculating the square root overflows SD59x18. error PRBMathSD59x18__SqrtOverflow(int256 x); /// @notice Emitted when addition overflows UD60x18. error PRBMathUD60x18__AddOverflow(uint256 x, uint256 y); /// @notice Emitted when ceiling a number overflows UD60x18. error PRBMathUD60x18__CeilOverflow(uint256 x); /// @notice Emitted when the input is greater than 133.084258667509499441. error PRBMathUD60x18__ExpInputTooBig(uint256 x); /// @notice Emitted when the input is greater than 192. error PRBMathUD60x18__Exp2InputTooBig(uint256 x); /// @notice Emitted when converting a basic integer to the fixed-point format format overflows UD60x18. error PRBMathUD60x18__FromUintOverflow(uint256 x); /// @notice Emitted when multiplying the inputs overflows UD60x18. error PRBMathUD60x18__GmOverflow(uint256 x, uint256 y); /// @notice Emitted when the input is less than 1. error PRBMathUD60x18__LogInputTooSmall(uint256 x); /// @notice Emitted when the calculating the square root overflows UD60x18. error PRBMathUD60x18__SqrtOverflow(uint256 x); /// @notice Emitted when subtraction underflows UD60x18. error PRBMathUD60x18__SubUnderflow(uint256 x, uint256 y); /// @dev Common mathematical functions used in both PRBMathSD59x18 and PRBMathUD60x18. Note that this shared library /// does not always assume the signed 59.18-decimal fixed-point or the unsigned 60.18-decimal fixed-point /// representation. When it does not, it is explicitly mentioned in the NatSpec documentation. library PRBMath { /// STRUCTS /// struct SD59x18 { int256 value; } struct UD60x18 { uint256 value; } /// STORAGE /// /// @dev How many trailing decimals can be represented. uint256 internal constant SCALE = 1e18; /// @dev Largest power of two divisor of SCALE. uint256 internal constant SCALE_LPOTD = 262144; /// @dev SCALE inverted mod 2^256. uint256 internal constant SCALE_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281; /// FUNCTIONS /// /// @notice Calculates the binary exponent of x using the binary fraction method. /// @dev Has to use 192.64-bit fixed-point numbers. /// See https://ethereum.stackexchange.com/a/96594/24693. /// @param x The exponent as an unsigned 192.64-bit fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function exp2(uint256 x) internal pure returns (uint256 result) { unchecked { // Start from 0.5 in the 192.64-bit fixed-point format. result = 0x800000000000000000000000000000000000000000000000; // Multiply the result by root(2, 2^-i) when the bit at position i is 1. None of the intermediary results overflows // because the initial result is 2^191 and all magic factors are less than 2^65. if (x & 0x8000000000000000 > 0) { result = (result * 0x16A09E667F3BCC909) >> 64; } if (x & 0x4000000000000000 > 0) { result = (result * 0x1306FE0A31B7152DF) >> 64; } if (x & 0x2000000000000000 > 0) { result = (result * 0x1172B83C7D517ADCE) >> 64; } if (x & 0x1000000000000000 > 0) { result = (result * 0x10B5586CF9890F62A) >> 64; } if (x & 0x800000000000000 > 0) { result = (result * 0x1059B0D31585743AE) >> 64; } if (x & 0x400000000000000 > 0) { result = (result * 0x102C9A3E778060EE7) >> 64; } if (x & 0x200000000000000 > 0) { result = (result * 0x10163DA9FB33356D8) >> 64; } if (x & 0x100000000000000 > 0) { result = (result * 0x100B1AFA5ABCBED61) >> 64; } if (x & 0x80000000000000 > 0) { result = (result * 0x10058C86DA1C09EA2) >> 64; } if (x & 0x40000000000000 > 0) { result = (result * 0x1002C605E2E8CEC50) >> 64; } if (x & 0x20000000000000 > 0) { result = (result * 0x100162F3904051FA1) >> 64; } if (x & 0x10000000000000 > 0) { result = (result * 0x1000B175EFFDC76BA) >> 64; } if (x & 0x8000000000000 > 0) { result = (result * 0x100058BA01FB9F96D) >> 64; } if (x & 0x4000000000000 > 0) { result = (result * 0x10002C5CC37DA9492) >> 64; } if (x & 0x2000000000000 > 0) { result = (result * 0x1000162E525EE0547) >> 64; } if (x & 0x1000000000000 > 0) { result = (result * 0x10000B17255775C04) >> 64; } if (x & 0x800000000000 > 0) { result = (result * 0x1000058B91B5BC9AE) >> 64; } if (x & 0x400000000000 > 0) { result = (result * 0x100002C5C89D5EC6D) >> 64; } if (x & 0x200000000000 > 0) { result = (result * 0x10000162E43F4F831) >> 64; } if (x & 0x100000000000 > 0) { result = (result * 0x100000B1721BCFC9A) >> 64; } if (x & 0x80000000000 > 0) { result = (result * 0x10000058B90CF1E6E) >> 64; } if (x & 0x40000000000 > 0) { result = (result * 0x1000002C5C863B73F) >> 64; } if (x & 0x20000000000 > 0) { result = (result * 0x100000162E430E5A2) >> 64; } if (x & 0x10000000000 > 0) { result = (result * 0x1000000B172183551) >> 64; } if (x & 0x8000000000 > 0) { result = (result * 0x100000058B90C0B49) >> 64; } if (x & 0x4000000000 > 0) { result = (result * 0x10000002C5C8601CC) >> 64; } if (x & 0x2000000000 > 0) { result = (result * 0x1000000162E42FFF0) >> 64; } if (x & 0x1000000000 > 0) { result = (result * 0x10000000B17217FBB) >> 64; } if (x & 0x800000000 > 0) { result = (result * 0x1000000058B90BFCE) >> 64; } if (x & 0x400000000 > 0) { result = (result * 0x100000002C5C85FE3) >> 64; } if (x & 0x200000000 > 0) { result = (result * 0x10000000162E42FF1) >> 64; } if (x & 0x100000000 > 0) { result = (result * 0x100000000B17217F8) >> 64; } if (x & 0x80000000 > 0) { result = (result * 0x10000000058B90BFC) >> 64; } if (x & 0x40000000 > 0) { result = (result * 0x1000000002C5C85FE) >> 64; } if (x & 0x20000000 > 0) { result = (result * 0x100000000162E42FF) >> 64; } if (x & 0x10000000 > 0) { result = (result * 0x1000000000B17217F) >> 64; } if (x & 0x8000000 > 0) { result = (result * 0x100000000058B90C0) >> 64; } if (x & 0x4000000 > 0) { result = (result * 0x10000000002C5C860) >> 64; } if (x & 0x2000000 > 0) { result = (result * 0x1000000000162E430) >> 64; } if (x & 0x1000000 > 0) { result = (result * 0x10000000000B17218) >> 64; } if (x & 0x800000 > 0) { result = (result * 0x1000000000058B90C) >> 64; } if (x & 0x400000 > 0) { result = (result * 0x100000000002C5C86) >> 64; } if (x & 0x200000 > 0) { result = (result * 0x10000000000162E43) >> 64; } if (x & 0x100000 > 0) { result = (result * 0x100000000000B1721) >> 64; } if (x & 0x80000 > 0) { result = (result * 0x10000000000058B91) >> 64; } if (x & 0x40000 > 0) { result = (result * 0x1000000000002C5C8) >> 64; } if (x & 0x20000 > 0) { result = (result * 0x100000000000162E4) >> 64; } if (x & 0x10000 > 0) { result = (result * 0x1000000000000B172) >> 64; } if (x & 0x8000 > 0) { result = (result * 0x100000000000058B9) >> 64; } if (x & 0x4000 > 0) { result = (result * 0x10000000000002C5D) >> 64; } if (x & 0x2000 > 0) { result = (result * 0x1000000000000162E) >> 64; } if (x & 0x1000 > 0) { result = (result * 0x10000000000000B17) >> 64; } if (x & 0x800 > 0) { result = (result * 0x1000000000000058C) >> 64; } if (x & 0x400 > 0) { result = (result * 0x100000000000002C6) >> 64; } if (x & 0x200 > 0) { result = (result * 0x10000000000000163) >> 64; } if (x & 0x100 > 0) { result = (result * 0x100000000000000B1) >> 64; } if (x & 0x80 > 0) { result = (result * 0x10000000000000059) >> 64; } if (x & 0x40 > 0) { result = (result * 0x1000000000000002C) >> 64; } if (x & 0x20 > 0) { result = (result * 0x10000000000000016) >> 64; } if (x & 0x10 > 0) { result = (result * 0x1000000000000000B) >> 64; } if (x & 0x8 > 0) { result = (result * 0x10000000000000006) >> 64; } if (x & 0x4 > 0) { result = (result * 0x10000000000000003) >> 64; } if (x & 0x2 > 0) { result = (result * 0x10000000000000001) >> 64; } if (x & 0x1 > 0) { result = (result * 0x10000000000000001) >> 64; } // We're doing two things at the same time: // // 1. Multiply the result by 2^n + 1, where "2^n" is the integer part and the one is added to account for // the fact that we initially set the result to 0.5. This is accomplished by subtracting from 191 // rather than 192. // 2. Convert the result to the unsigned 60.18-decimal fixed-point format. // // This works because 2^(191-ip) = 2^ip / 2^191, where "ip" is the integer part "2^n". result *= SCALE; result >>= (191 - (x >> 64)); } } /// @notice Finds the zero-based index of the first one in the binary representation of x. /// @dev See the note on msb in the "Find First Set" Wikipedia article https://en.wikipedia.org/wiki/Find_first_set /// @param x The uint256 number for which to find the index of the most significant bit. /// @return msb The index of the most significant bit as an uint256. function mostSignificantBit(uint256 x) internal pure returns (uint256 msb) { if (x >= 2**128) { x >>= 128; msb += 128; } if (x >= 2**64) { x >>= 64; msb += 64; } if (x >= 2**32) { x >>= 32; msb += 32; } if (x >= 2**16) { x >>= 16; msb += 16; } if (x >= 2**8) { x >>= 8; msb += 8; } if (x >= 2**4) { x >>= 4; msb += 4; } if (x >= 2**2) { x >>= 2; msb += 2; } if (x >= 2**1) { // No need to shift x any more. msb += 1; } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv. /// /// Requirements: /// - The denominator cannot be zero. /// - The result must fit within uint256. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The multiplicand as an uint256. /// @param y The multiplier as an uint256. /// @param denominator The divisor as an uint256. /// @return result The result as an uint256. function mulDiv( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 result) { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. uint256 prod0; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { unchecked { result = prod0 / denominator; } return result; } // Make sure the result is less than 2^256. Also prevents denominator == 0. if (prod1 >= denominator) { revert PRBMath__MulDivOverflow(prod1, denominator); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. unchecked { // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 lpotdod = denominator & (~denominator + 1); assembly { // Divide denominator by lpotdod. denominator := div(denominator, lpotdod) // Divide [prod1 prod0] by lpotdod. prod0 := div(prod0, lpotdod) // Flip lpotdod such that it is 2^256 / lpotdod. If lpotdod is zero, then it becomes one. lpotdod := add(div(sub(0, lpotdod), lpotdod), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * lpotdod; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /// @notice Calculates floor(x*y÷1e18) with full precision. /// /// @dev Variant of "mulDiv" with constant folding, i.e. in which the denominator is always 1e18. Before returning the /// final result, we add 1 if (x * y) % SCALE >= HALF_SCALE. Without this, 6.6e-19 would be truncated to 0 instead of /// being rounded to 1e-18. See "Listing 6" and text above it at https://accu.org/index.php/journals/1717. /// /// Requirements: /// - The result must fit within uint256. /// /// Caveats: /// - The body is purposely left uncommented; see the NatSpec comments in "PRBMath.mulDiv" to understand how this works. /// - It is assumed that the result can never be type(uint256).max when x and y solve the following two equations: /// 1. x * y = type(uint256).max * SCALE /// 2. (x * y) % SCALE >= SCALE / 2 /// /// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number. /// @param y The multiplier as an unsigned 60.18-decimal fixed-point number. /// @return result The result as an unsigned 60.18-decimal fixed-point number. function mulDivFixedPoint(uint256 x, uint256 y) internal pure returns (uint256 result) { uint256 prod0; uint256 prod1; assembly { let mm := mulmod(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } if (prod1 >= SCALE) { revert PRBMath__MulDivFixedPointOverflow(prod1); } uint256 remainder; uint256 roundUpUnit; assembly { remainder := mulmod(x, y, SCALE) roundUpUnit := gt(remainder, 499999999999999999) } if (prod1 == 0) { unchecked { result = (prod0 / SCALE) + roundUpUnit; return result; } } assembly { result := add( mul( or( div(sub(prod0, remainder), SCALE_LPOTD), mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, SCALE_LPOTD), SCALE_LPOTD), 1)) ), SCALE_INVERSE ), roundUpUnit ) } } /// @notice Calculates floor(x*y÷denominator) with full precision. /// /// @dev An extension of "mulDiv" for signed numbers. Works by computing the signs and the absolute values separately. /// /// Requirements: /// - None of the inputs can be type(int256).min. /// - The result must fit within int256. /// /// @param x The multiplicand as an int256. /// @param y The multiplier as an int256. /// @param denominator The divisor as an int256. /// @return result The result as an int256. function mulDivSigned( int256 x, int256 y, int256 denominator ) internal pure returns (int256 result) { if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) { revert PRBMath__MulDivSignedInputTooSmall(); } // Get hold of the absolute values of x, y and the denominator. uint256 ax; uint256 ay; uint256 ad; unchecked { ax = x < 0 ? uint256(-x) : uint256(x); ay = y < 0 ? uint256(-y) : uint256(y); ad = denominator < 0 ? uint256(-denominator) : uint256(denominator); } // Compute the absolute value of (x*y)÷denominator. The result must fit within int256. uint256 rAbs = mulDiv(ax, ay, ad); if (rAbs > uint256(type(int256).max)) { revert PRBMath__MulDivSignedOverflow(rAbs); } // Get the signs of x, y and the denominator. uint256 sx; uint256 sy; uint256 sd; assembly { sx := sgt(x, sub(0, 1)) sy := sgt(y, sub(0, 1)) sd := sgt(denominator, sub(0, 1)) } // XOR over sx, sy and sd. This is checking whether there are one or three negative signs in the inputs. // If yes, the result should be negative. result = sx ^ sy ^ sd == 0 ? -int256(rAbs) : int256(rAbs); } /// @notice Calculates the square root of x, rounding down. /// @dev Uses the Babylonian method https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method. /// /// Caveats: /// - This function does not work with fixed-point numbers. /// /// @param x The uint256 number for which to calculate the square root. /// @return result The result as an uint256. function sqrt(uint256 x) internal pure returns (uint256 result) { if (x == 0) { return 0; } // Set the initial guess to the least power of two that is greater than or equal to sqrt(x). uint256 xAux = uint256(x); result = 1; if (xAux >= 0x100000000000000000000000000000000) { xAux >>= 128; result <<= 64; } if (xAux >= 0x10000000000000000) { xAux >>= 64; result <<= 32; } if (xAux >= 0x100000000) { xAux >>= 32; result <<= 16; } if (xAux >= 0x10000) { xAux >>= 16; result <<= 8; } if (xAux >= 0x100) { xAux >>= 8; result <<= 4; } if (xAux >= 0x10) { xAux >>= 4; result <<= 2; } if (xAux >= 0x8) { result <<= 1; } // The operations can never overflow because the result is max 2^127 when it enters this block. unchecked { result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; result = (result + x / result) >> 1; // Seven iterations should be enough uint256 roundedDownResult = x / result; return result >= roundedDownResult ? roundedDownResult : result; } } }
{ "remappings": [ "@solmate/=lib/solmate/src/", "@std/=lib/forge-std/src/", "@clones/=lib/clones-with-immutable-args/src/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@prb-math/=lib/prb-math/", "@base64-sol/=lib/base64/", "src/=src/", "base64/=lib/base64/", "clones-with-immutable-args/=lib/clones-with-immutable-args/src/", "ds-test/=lib/clones-with-immutable-args/lib/ds-test/src/", "forge-std/=lib/forge-std/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "prb-math/=lib/prb-math/contracts/" ], "optimizer": { "enabled": true, "runs": 0 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "paris", "libraries": { "src/libraries/external/BorrowerActions.sol": { "BorrowerActions": "0x37ed1d5d903adddda4fcc6d003b840c883d05402" }, "src/libraries/external/KickerActions.sol": { "KickerActions": "0xaf983b52aec0f6e127ddd51ce3367552d7916387" }, "src/libraries/external/LPActions.sol": { "LPActions": "0xac8892dd81ee0fec9c11ecf6ef3bd1a773d003fb" }, "src/libraries/external/LenderActions.sol": { "LenderActions": "0xdce7fd455e1a65b40186292657e6231f87d81c49" }, "src/libraries/external/PoolCommons.sol": { "PoolCommons": "0xe88aaf46c9124b7b08c2dcc2505429ce72979648" }, "src/libraries/external/PositionNFTSVG.sol": { "PositionNFTSVG": "0x83fcb77b91288173175a4ac70f848d57ff95a9dd" }, "src/libraries/external/SettlerActions.sol": { "SettlerActions": "0x4418b6a45d785b85e87c022d99b0ff9e267268fe" }, "src/libraries/external/TakerActions.sol": { "TakerActions": "0x9c09a67a622650037fe70f21a5f6770a363009e1" } } }
[{"inputs":[],"name":"AddAboveAuctionPrice","type":"error"},{"inputs":[],"name":"AddAboveAuctionPrice","type":"error"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AmountLTMinDebt","type":"error"},{"inputs":[],"name":"AuctionActive","type":"error"},{"inputs":[],"name":"AuctionActive","type":"error"},{"inputs":[],"name":"AuctionNotClearable","type":"error"},{"inputs":[],"name":"AuctionNotCleared","type":"error"},{"inputs":[],"name":"AuctionNotCleared","type":"error"},{"inputs":[],"name":"AuctionNotTakeable","type":"error"},{"inputs":[],"name":"AuctionPriceGtBucketPrice","type":"error"},{"inputs":[],"name":"BorrowerNotSender","type":"error"},{"inputs":[],"name":"BorrowerOk","type":"error"},{"inputs":[],"name":"BorrowerUnderCollateralized","type":"error"},{"inputs":[],"name":"BucketBankruptcyBlock","type":"error"},{"inputs":[],"name":"BucketIndexOutOfBounds","type":"error"},{"inputs":[],"name":"CannotMergeToHigherPrice","type":"error"},{"inputs":[],"name":"DustAmountNotExceeded","type":"error"},{"inputs":[],"name":"FlashloanCallbackFailed","type":"error"},{"inputs":[],"name":"FlashloanIncorrectBalance","type":"error"},{"inputs":[],"name":"FlashloanUnavailableForToken","type":"error"},{"inputs":[],"name":"InsufficientCollateral","type":"error"},{"inputs":[],"name":"InsufficientLP","type":"error"},{"inputs":[],"name":"InsufficientLiquidity","type":"error"},{"inputs":[],"name":"InvalidAllowancesInput","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidIndex","type":"error"},{"inputs":[],"name":"LUPBelowHTP","type":"error"},{"inputs":[],"name":"LimitIndexExceeded","type":"error"},{"inputs":[],"name":"MoveToSameIndex","type":"error"},{"inputs":[],"name":"NoAllowance","type":"error"},{"inputs":[],"name":"NoAuction","type":"error"},{"inputs":[],"name":"NoClaim","type":"error"},{"inputs":[],"name":"NoDebt","type":"error"},{"inputs":[],"name":"NoReserves","type":"error"},{"inputs":[],"name":"NoReservesAuction","type":"error"},{"inputs":[],"name":"PRBMathSD59x18__DivInputTooSmall","type":"error"},{"inputs":[{"internalType":"uint256","name":"rAbs","type":"uint256"}],"name":"PRBMathSD59x18__DivOverflow","type":"error"},{"inputs":[{"internalType":"int256","name":"x","type":"int256"}],"name":"PRBMathSD59x18__Exp2InputTooBig","type":"error"},{"inputs":[{"internalType":"int256","name":"x","type":"int256"}],"name":"PRBMathSD59x18__FromIntOverflow","type":"error"},{"inputs":[{"internalType":"int256","name":"x","type":"int256"}],"name":"PRBMathSD59x18__FromIntUnderflow","type":"error"},{"inputs":[{"internalType":"int256","name":"x","type":"int256"}],"name":"PRBMathSD59x18__LogInputTooSmall","type":"error"},{"inputs":[],"name":"PRBMathSD59x18__MulInputTooSmall","type":"error"},{"inputs":[{"internalType":"uint256","name":"rAbs","type":"uint256"}],"name":"PRBMathSD59x18__MulOverflow","type":"error"},{"inputs":[{"internalType":"int256","name":"x","type":"int256"}],"name":"PRBMathSD59x18__SqrtNegativeInput","type":"error"},{"inputs":[{"internalType":"int256","name":"x","type":"int256"}],"name":"PRBMathSD59x18__SqrtOverflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"prod1","type":"uint256"}],"name":"PRBMath__MulDivFixedPointOverflow","type":"error"},{"inputs":[{"internalType":"uint256","name":"prod1","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"name":"PRBMath__MulDivOverflow","type":"error"},{"inputs":[],"name":"PriceBelowLUP","type":"error"},{"inputs":[],"name":"RemoveDepositLockedByAuctionDebt","type":"error"},{"inputs":[],"name":"RemoveDepositLockedByAuctionDebt","type":"error"},{"inputs":[],"name":"ReserveAuctionTooSoon","type":"error"},{"inputs":[],"name":"TransactionExpired","type":"error"},{"inputs":[],"name":"TransactionExpired","type":"error"},{"inputs":[],"name":"TransferToSameOwner","type":"error"},{"inputs":[],"name":"TransferorNotApproved","type":"error"},{"inputs":[],"name":"ZeroDebtToCollateral","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"actor","type":"address"},{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpAwarded","type":"uint256"}],"name":"AddCollateral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lender","type":"address"},{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpAwarded","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lup","type":"uint256"}],"name":"AddQuoteToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lender","type":"address"},{"indexed":false,"internalType":"address[]","name":"transferors","type":"address[]"}],"name":"ApproveLPTransferors","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"AuctionNFTSettle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"}],"name":"AuctionSettle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"kicker","type":"address"},{"indexed":true,"internalType":"address","name":"reciever","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"BondWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpForfeited","type":"uint256"}],"name":"BucketBankruptcy","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bondChange","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isReward","type":"bool"}],"name":"BucketTake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"taker","type":"address"},{"indexed":true,"internalType":"address","name":"kicker","type":"address"},{"indexed":false,"internalType":"uint256","name":"lpAwardedTaker","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpAwardedKicker","type":"uint256"}],"name":"BucketTakeLPAwarded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"DecreaseLPAllowance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountBorrowed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralPledged","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lup","type":"uint256"}],"name":"DrawDebt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Flashloan","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"IncreaseLPAllowance","type":"event"},{"anonymous":false,"inputs":[],"name":"InterestUpdateFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"debt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bond","type":"uint256"}],"name":"Kick","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"claimableReservesRemaining","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentBurnEpoch","type":"uint256"}],"name":"KickReserveAuction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"}],"name":"LoanStamped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lender","type":"address"},{"indexed":true,"internalType":"uint256","name":"from","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"to","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpRedeemedFrom","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpAwardedTo","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lup","type":"uint256"}],"name":"MoveQuoteToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"claimer","type":"address"},{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpRedeemed","type":"uint256"}],"name":"RemoveCollateral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lender","type":"address"},{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lpRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lup","type":"uint256"}],"name":"RemoveQuoteToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"quoteRepaid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralPulled","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lup","type":"uint256"}],"name":"RepayDebt","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"claimableReservesRemaining","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"auctionPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"currentBurnEpoch","type":"uint256"}],"name":"ReserveAuction","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"ResetInterestRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"indexes","type":"uint256[]"}],"name":"RevokeLPAllowance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lender","type":"address"},{"indexed":false,"internalType":"address[]","name":"transferors","type":"address[]"}],"name":"RevokeLPTransferors","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"settledDebt","type":"uint256"}],"name":"Settle","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bondChange","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isReward","type":"bool"}],"name":"Take","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"lp","type":"uint256"}],"name":"TransferLP","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"UpdateInterestRate","type":"event"},{"inputs":[{"internalType":"uint256","name":"amountToAdd_","type":"uint256"},{"internalType":"uint256","name":"index_","type":"uint256"},{"internalType":"uint256","name":"expiry_","type":"uint256"}],"name":"addCollateral","outputs":[{"internalType":"uint256","name":"bucketLP_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"index_","type":"uint256"},{"internalType":"uint256","name":"expiry_","type":"uint256"}],"name":"addQuoteToken","outputs":[{"internalType":"uint256","name":"bucketLP_","type":"uint256"},{"internalType":"uint256","name":"addedAmount_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"transferors_","type":"address[]"}],"name":"approveLPTransferors","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"approvedTransferors","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrower_","type":"address"}],"name":"auctionInfo","outputs":[{"internalType":"address","name":"kicker_","type":"address"},{"internalType":"uint256","name":"bondFactor_","type":"uint256"},{"internalType":"uint256","name":"bondSize_","type":"uint256"},{"internalType":"uint256","name":"kickTime_","type":"uint256"},{"internalType":"uint256","name":"referencePrice_","type":"uint256"},{"internalType":"uint256","name":"neutralPrice_","type":"uint256"},{"internalType":"uint256","name":"debtToCollateral_","type":"uint256"},{"internalType":"address","name":"head_","type":"address"},{"internalType":"address","name":"next_","type":"address"},{"internalType":"address","name":"prev_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrower_","type":"address"}],"name":"borrowerInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"bucketIndex_","type":"uint256"}],"name":"bucketCollateralDust","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"bucketExchangeRate","outputs":[{"internalType":"uint256","name":"exchangeRate_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"bucketInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrowerAddress_","type":"address"},{"internalType":"bool","name":"depositTake_","type":"bool"},{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"bucketTake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"burnEventEpoch_","type":"uint256"}],"name":"burnInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collateralAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"collateralScale","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"currentBurnEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"debtInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256[]","name":"indexes_","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts_","type":"uint256[]"}],"name":"decreaseLPAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"debt_","type":"uint256"}],"name":"depositIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"depositScale","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"depositUpToIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositUtilization","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrowerAddress_","type":"address"},{"internalType":"uint256","name":"amountToBorrow_","type":"uint256"},{"internalType":"uint256","name":"limitIndex_","type":"uint256"},{"internalType":"uint256","name":"collateralToPledge_","type":"uint256"}],"name":"drawDebt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emasInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"flashFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC3156FlashBorrower","name":"receiver_","type":"address"},{"internalType":"address","name":"token_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"bytes","name":"data_","type":"bytes"}],"name":"flashLoan","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256[]","name":"indexes_","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts_","type":"uint256[]"}],"name":"increaseLPAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"inflatorInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"rate_","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"interestRateInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"borrower_","type":"address"},{"internalType":"uint256","name":"npLimitIndex_","type":"uint256"}],"name":"kick","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"kickReserveAuction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"kicker_","type":"address"}],"name":"kickerInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"},{"internalType":"address","name":"lender_","type":"address"}],"name":"lenderInfo","outputs":[{"internalType":"uint256","name":"lpBalance_","type":"uint256"},{"internalType":"uint256","name":"depositTime_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"},{"internalType":"uint256","name":"npLimitIndex_","type":"uint256"}],"name":"lenderKick","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"loanId_","type":"uint256"}],"name":"loanInfo","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"loansInfo","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index_","type":"uint256"},{"internalType":"address","name":"spender_","type":"address"},{"internalType":"address","name":"owner_","type":"address"}],"name":"lpAllowance","outputs":[{"internalType":"uint256","name":"allowance_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"}],"name":"maxFlashLoan","outputs":[{"internalType":"uint256","name":"maxLoan_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxAmount_","type":"uint256"},{"internalType":"uint256","name":"fromIndex_","type":"uint256"},{"internalType":"uint256","name":"toIndex_","type":"uint256"},{"internalType":"uint256","name":"expiry_","type":"uint256"}],"name":"moveQuoteToken","outputs":[{"internalType":"uint256","name":"fromBucketLP_","type":"uint256"},{"internalType":"uint256","name":"toBucketLP_","type":"uint256"},{"internalType":"uint256","name":"movedAmount_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pledgedCollateral","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolType","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"quoteTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"quoteTokenScale","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxAmount_","type":"uint256"},{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"removeCollateral","outputs":[{"internalType":"uint256","name":"removedAmount_","type":"uint256"},{"internalType":"uint256","name":"redeemedLP_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxAmount_","type":"uint256"},{"internalType":"uint256","name":"index_","type":"uint256"}],"name":"removeQuoteToken","outputs":[{"internalType":"uint256","name":"removedAmount_","type":"uint256"},{"internalType":"uint256","name":"redeemedLP_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"borrowerAddress_","type":"address"},{"internalType":"uint256","name":"maxQuoteTokenAmountToRepay_","type":"uint256"},{"internalType":"uint256","name":"collateralAmountToPull_","type":"uint256"},{"internalType":"address","name":"collateralReceiver_","type":"address"},{"internalType":"uint256","name":"limitIndex_","type":"uint256"}],"name":"repayDebt","outputs":[{"internalType":"uint256","name":"amountRepaid_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reservesInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender_","type":"address"},{"internalType":"uint256[]","name":"indexes_","type":"uint256[]"}],"name":"revokeLPAllowance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"transferors_","type":"address[]"}],"name":"revokeLPTransferors","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"borrowerAddress_","type":"address"},{"internalType":"uint256","name":"maxDepth_","type":"uint256"}],"name":"settle","outputs":[{"internalType":"uint256","name":"collateralSettled_","type":"uint256"},{"internalType":"bool","name":"isBorrowerSettled_","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stampLoan","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"borrowerAddress_","type":"address"},{"internalType":"uint256","name":"maxAmount_","type":"uint256"},{"internalType":"address","name":"callee_","type":"address"},{"internalType":"bytes","name":"data_","type":"bytes"}],"name":"take","outputs":[{"internalType":"uint256","name":"collateralTaken_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"maxAmount_","type":"uint256"}],"name":"takeReserves","outputs":[{"internalType":"uint256","name":"amount_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalAuctionsInPool","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalT0Debt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalT0DebtInAuction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"newOwner_","type":"address"},{"internalType":"uint256[]","name":"indexes_","type":"uint256[]"}],"name":"transferLP","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateInterest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient_","type":"address"},{"internalType":"uint256","name":"maxAmount_","type":"uint256"}],"name":"withdrawBonds","outputs":[{"internalType":"uint256","name":"withdrawnAmount_","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.