Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"@openzeppelin/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [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://consensys.net/diligence/blog/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.8.0/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);
}
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"contracts/interfaces/IPositionManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
/// @title IPositionManager Interface
/// @notice Interface for the PositionManager contract which manages individual positions
interface IPositionManager {
/// @notice Emitted when a redemption occurs
/// @param _attemptedStableAmount The amount of stable tokens attempted to redeem
/// @param _actualStableAmount The actual amount of stable tokens redeemed
/// @param _CollateralSent The amount of collateral sent to the redeemer
/// @param _CollateralFee The fee paid in collateral for the redemption
event Redemption(uint _attemptedStableAmount, uint _actualStableAmount, uint _CollateralSent, uint _CollateralFee);
/// @notice Emitted when total stakes are updated
/// @param _newTotalStakes The new total stakes value
event TotalStakesUpdated(uint _newTotalStakes);
/// @notice Emitted when system snapshots are updated
/// @param _totalStakesSnapshot The new total stakes snapshot
/// @param _totalCollateralSnapshot The new total collateral snapshot
event SystemSnapshotsUpdated(uint _totalStakesSnapshot, uint _totalCollateralSnapshot);
/// @notice Emitted when L terms are updated
/// @param _L_Collateral The new L_Collateral value
/// @param _L_STABLE The new L_STABLE value
event LTermsUpdated(uint _L_Collateral, uint _L_STABLE);
/// @notice Emitted when position snapshots are updated
/// @param _L_Collateral The new L_Collateral value for the position
/// @param _L_STABLEDebt The new L_STABLEDebt value for the position
event PositionSnapshotsUpdated(uint _L_Collateral, uint _L_STABLEDebt);
/// @notice Emitted when a position's index is updated
/// @param _borrower The address of the position owner
/// @param _newIndex The new index value
event PositionIndexUpdated(address _borrower, uint _newIndex);
/// @notice Get the total count of position owners
/// @return The number of position owners
function getPositionOwnersCount() external view returns (uint);
/// @notice Get a position owner's address by index
/// @param _index The index in the position owners array
/// @return The address of the position owner
function getPositionFromPositionOwnersArray(uint _index) external view returns (address);
/// @notice Get the nominal ICR (Individual Collateral Ratio) of a position
/// @param _borrower The address of the position owner
/// @return The nominal ICR of the position
function getNominalICR(address _borrower) external view returns (uint);
/// @notice Get the current ICR of a position
/// @param _borrower The address of the position owner
/// @param _price The current price of the collateral
/// @return The current ICR of the position
function getCurrentICR(address _borrower, uint _price) external view returns (uint);
/// @notice Liquidate a single position
/// @param _borrower The address of the position owner to liquidate
function liquidate(address _borrower) external;
/// @notice Liquidate multiple positions
/// @param _n The number of positions to attempt to liquidate
function liquidatePositions(uint _n) external;
/// @notice Batch liquidate a specific set of positions
/// @param _positionArray An array of position owner addresses to liquidate
function batchLiquidatePositions(address[] calldata _positionArray) external;
/// @notice Queue a redemption request
/// @param _stableAmount The amount of stable tokens to queue for redemption
function queueRedemption(uint _stableAmount) external;
/// @notice Redeem collateral for stable tokens
/// @param _stableAmount The amount of stable tokens to redeem
/// @param _firstRedemptionHint The address of the first position to consider for redemption
/// @param _upperPartialRedemptionHint The address of the position just above the partial redemption
/// @param _lowerPartialRedemptionHint The address of the position just below the partial redemption
/// @param _partialRedemptionHintNICR The nominal ICR of the partial redemption hint
/// @param _maxIterations The maximum number of iterations to perform in the redemption algorithm
/// @param _maxFee The maximum acceptable fee percentage for the redemption
function redeemCollateral(
uint _stableAmount,
address _firstRedemptionHint,
address _upperPartialRedemptionHint,
address _lowerPartialRedemptionHint,
uint _partialRedemptionHintNICR,
uint _maxIterations,
uint _maxFee
) external;
/// @notice Update the stake and total stakes for a position
/// @param _borrower The address of the position owner
/// @return The new stake value
function updateStakeAndTotalStakes(address _borrower) external returns (uint);
/// @notice Update the reward snapshots for a position
/// @param _borrower The address of the position owner
function updatePositionRewardSnapshots(address _borrower) external;
/// @notice Add a position owner to the array of position owners
/// @param _borrower The address of the position owner
/// @return index The index of the new position owner in the array
function addPositionOwnerToArray(address _borrower) external returns (uint index);
/// @notice Apply pending rewards to a position
/// @param _borrower The address of the position owner
function applyPendingRewards(address _borrower) external;
/// @notice Get the pending collateral reward for a position
/// @param _borrower The address of the position owner
/// @return The amount of pending collateral reward
function getPendingCollateralReward(address _borrower) external view returns (uint);
/// @notice Get the pending stable debt reward for a position
/// @param _borrower The address of the position owner
/// @return The amount of pending stable debt reward
function getPendingStableDebtReward(address _borrower) external view returns (uint);
/// @notice Check if a position has pending rewards
/// @param _borrower The address of the position owner
/// @return True if the position has pending rewards, false otherwise
function hasPendingRewards(address _borrower) external view returns (bool);
/// @notice Get the entire debt and collateral for a position, including pending rewards
/// @param _borrower The address of the position owner
/// @return debt The total debt of the position
/// @return coll The total collateral of the position
/// @return pendingStableDebtReward The pending stable debt reward
/// @return pendingCollateralReward The pending collateral reward
function getEntireDebtAndColl(address _borrower)
external view returns (uint debt, uint coll, uint pendingStableDebtReward, uint pendingCollateralReward);
/// @notice Close a position
/// @param _borrower The address of the position owner
function closePosition(address _borrower) external;
/// @notice Remove the stake for a position
/// @param _borrower The address of the position owner
function removeStake(address _borrower) external;
/// @notice Get the current redemption rate
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The current redemption rate
function getRedemptionRate(uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Get the redemption rate with decay
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The redemption rate with decay applied
function getRedemptionRateWithDecay(uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Get the redemption fee with decay
/// @param _CollateralDrawn The amount of collateral drawn
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The redemption fee with decay applied
function getRedemptionFeeWithDecay(uint _CollateralDrawn, uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Get the current borrowing rate
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The current borrowing rate
function getBorrowingRate(uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Get the borrowing rate with decay
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The borrowing rate with decay applied
function getBorrowingRateWithDecay(uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Get the borrowing fee
/// @param stableDebt The amount of stable debt
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The borrowing fee
function getBorrowingFee(uint stableDebt, uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Get the borrowing fee with decay
/// @param _stableDebt The amount of stable debt
/// @param suggestedAdditiveFeePCT The suggested additive fee percentage
/// @return The borrowing fee with decay applied
function getBorrowingFeeWithDecay(uint _stableDebt, uint suggestedAdditiveFeePCT) external view returns (uint);
/// @notice Decay the base rate from borrowing
function decayBaseRateFromBorrowing() external;
/// @notice Get the status of a position
/// @param _borrower The address of the position owner
/// @return The status of the position
function getPositionStatus(address _borrower) external view returns (uint);
/// @notice Get the stake of a position
/// @param _borrower The address of the position owner
/// @return The stake of the position
function getPositionStake(address _borrower) external view returns (uint);
/// @notice Get the debt of a position
/// @param _borrower The address of the position owner
/// @return The debt of the position
function getPositionDebt(address _borrower) external view returns (uint);
/// @notice Get the collateral of a position
/// @param _borrower The address of the position owner
/// @return The collateral of the position
function getPositionColl(address _borrower) external view returns (uint);
/// @notice Set the status of a position
/// @param _borrower The address of the position owner
/// @param num The new status value
function setPositionStatus(address _borrower, uint num) external;
/// @notice Increase the collateral of a position
/// @param _borrower The address of the position owner
/// @param _collIncrease The amount of collateral to increase
/// @return The new collateral amount
function increasePositionColl(address _borrower, uint _collIncrease) external returns (uint);
/// @notice Decrease the collateral of a position
/// @param _borrower The address of the position owner
/// @param _collDecrease The amount of collateral to decrease
/// @return The new collateral amount
function decreasePositionColl(address _borrower, uint _collDecrease) external returns (uint);
/// @notice Increase the debt of a position
/// @param _borrower The address of the position owner
/// @param _debtIncrease The amount of debt to increase
/// @return The new debt amount
function increasePositionDebt(address _borrower, uint _debtIncrease) external returns (uint);
/// @notice Decrease the debt of a position
/// @param _borrower The address of the position owner
/// @param _debtDecrease The amount of debt to decrease
/// @return The new debt amount
function decreasePositionDebt(address _borrower, uint _debtDecrease) external returns (uint);
/// @notice Get the entire debt of the system
/// @return total The total debt in the system
function getEntireDebt() external view returns (uint total);
/// @notice Get the entire collateral in the system
/// @return total The total collateral in the system
function getEntireCollateral() external view returns (uint total);
/// @notice Get the Total Collateral Ratio (TCR) of the system
/// @param _price The current price of the collateral
/// @return TCR The Total Collateral Ratio
function getTCR(uint _price) external view returns(uint TCR);
/// @notice Check if the system is in Recovery Mode
/// @param _price The current price of the collateral
/// @return True if the system is in Recovery Mode, false otherwise
function checkRecoveryMode(uint _price) external returns(bool);
/// @notice Check if the position manager is in sunset mode
/// @return True if the position manager is in sunset mode, false otherwise
function isSunset() external returns(bool);
}"
},
"contracts/interfaces/ISortedPositions.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
/// @title ISortedPositions Interface
/// @notice Interface for a sorted list of positions, ordered by their Individual Collateral Ratio (ICR)
interface ISortedPositions {
/// @notice Emitted when the PositionManager address is changed
/// @param _positionManagerAddress The new address of the PositionManager
event PositionManagerAddressChanged(address _positionManagerAddress);
/// @notice Emitted when the PositionController address is changed
/// @param _positionControllerAddress The new address of the PositionController
event PositionControllerAddressChanged(address _positionControllerAddress);
/// @notice Emitted when a new node (position) is added to the list
/// @param _id The address of the new position
/// @param _NICR The Nominal Individual Collateral Ratio of the new position
event NodeAdded(address _id, uint _NICR);
/// @notice Emitted when a node (position) is removed from the list
/// @param _id The address of the removed position
event NodeRemoved(address _id);
/// @notice Sets the parameters for the sorted list
/// @param _size The maximum size of the list
/// @param _positionManagerAddress The address of the PositionManager contract
/// @param _positionControllerAddress The address of the PositionController contract
function setParams(uint256 _size, address _positionManagerAddress, address _positionControllerAddress) external;
/// @notice Inserts a new node (position) into the list
/// @param _id The address of the new position
/// @param _ICR The Individual Collateral Ratio of the new position
/// @param _prevId The address of the previous node in the insertion position
/// @param _nextId The address of the next node in the insertion position
function insert(address _id, uint256 _ICR, address _prevId, address _nextId) external;
/// @notice Removes a node (position) from the list
/// @param _id The address of the position to remove
function remove(address _id) external;
/// @notice Re-inserts a node (position) into the list with a new ICR
/// @param _id The address of the position to re-insert
/// @param _newICR The new Individual Collateral Ratio of the position
/// @param _prevId The address of the previous node in the new insertion position
/// @param _nextId The address of the next node in the new insertion position
function reInsert(address _id, uint256 _newICR, address _prevId, address _nextId) external;
/// @notice Checks if a position is in the list
/// @param _id The address of the position to check
/// @return bool True if the position is in the list, false otherwise
function contains(address _id) external view returns (bool);
/// @notice Checks if the list is full
/// @return bool True if the list is full, false otherwise
function isFull() external view returns (bool);
/// @notice Checks if the list is empty
/// @return bool True if the list is empty, false otherwise
function isEmpty() external view returns (bool);
/// @notice Gets the current size of the list
/// @return uint256 The current number of positions in the list
function getSize() external view returns (uint256);
/// @notice Gets the maximum size of the list
/// @return uint256 The maximum number of positions the list can hold
function getMaxSize() external view returns (uint256);
/// @notice Gets the first position in the list (highest ICR)
/// @return address The address of the first position
function getFirst() external view returns (address);
/// @notice Gets the last position in the list (lowest ICR)
/// @return address The address of the last position
function getLast() external view returns (address);
/// @notice Gets the next position in the list after a given position
/// @param _id The address of the current position
/// @return address The address of the next position
function getNext(address _id) external view returns (address);
/// @notice Gets the previous position in the list before a given position
/// @param _id The address of the current position
/// @return address The address of the previous position
function getPrev(address _id) external view returns (address);
/// @notice Checks if a given insertion position is valid for a new ICR
/// @param _ICR The ICR of the position to insert
/// @param _prevId The address of the proposed previous node
/// @param _nextId The address of the proposed next node
/// @return bool True if the insertion position is valid, false otherwise
function validInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (bool);
/// @notice Finds the correct insertion position for a given ICR
/// @param _ICR The ICR of the position to insert
/// @param _prevId A hint for the previous node
/// @param _nextId A hint for the next node
/// @return address The address of the previous node for insertion
/// @return address The address of the next node for insertion
function findInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (address, address);
}"
},
"contracts/stable/collateral/instance/SortedPositions.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../../../interfaces/ISortedPositions.sol";
import "../../../interfaces/IPositionManager.sol";
/*
* A sorted doubly linked list with nodes sorted in descending order.
*
* Nodes map to active Positions in the system - the ID property is the address of a Position owner.
* Nodes are ordered according to their current nominal individual collateral ratio (NICR),
* which is like the ICR but without the price, i.e., just collateral / debt.
*
* The list optionally accepts insert position hints.
*
* NICRs are computed dynamically at runtime, and not stored on the Node. This is because NICRs of active Positions
* change dynamically as liquidation events occur.
*
* The list relies on the fact that liquidation events preserve ordering: a liquidation decreases the NICRs of all active Positions,
* but maintains their order. A node inserted based on current NICR will maintain the correct position,
* relative to it's peers, as rewards accumulate, as long as it's raw collateral and debt have not changed.
* Thus, Nodes remain sorted by current NICR.
*
* Nodes need only be re-inserted upon a Position operation - when the owner adds or removes collateral or debt
* to their position.
*
* The list is a modification of the following audited SortedDoublyLinkedList:
* https://github.com/livepeer/protocol/blob/master/contracts/libraries/SortedDoublyLL.sol
*
*
* - Keys have been removed from nodes
*
* - Ordering checks for insertion are performed by comparing an NICR argument to the current NICR, calculated at runtime.
* The list relies on the property that ordering by ICR is maintained as the <COLLATERAL>:USD price varies.
*
* - Public functions with parameters have been made internal to save gas, and given an external wrapper function for external access
*/
contract SortedPositions is Ownable, ISortedPositions {
string constant public NAME = "SortedPositions";
address public positionControllerAddress;
IPositionManager public positionManager;
// Information for a node in the list
struct Node {
bool exists;
address nextId; // Id of next node (smaller NICR) in the list
address prevId; // Id of previous node (larger NICR) in the list
}
// Information for the list
struct Data {
address head; // Head of the list. Also the node in the list with the largest NICR
address tail; // Tail of the list. Also the node in the list with the smallest NICR
uint256 maxSize; // Maximum size of the list
uint256 size; // Current size of the list
mapping (address => Node) nodes; // Track the corresponding ids for each node in the list
}
Data public data;
function setParams(uint256 _size, address _positionManagerAddress, address _positionControllerAddress) external override onlyOwner {
require(_size > 0, "SortedPositions: Size can't be zero");
require(Address.isContract(_positionManagerAddress), "_positionManagerAddress is not a contract");
require(Address.isContract(_positionControllerAddress), "_positionControllerAddress is not a contract");
data.maxSize = _size;
positionManager = IPositionManager(_positionManagerAddress);
positionControllerAddress = _positionControllerAddress;
emit PositionManagerAddressChanged(_positionManagerAddress);
emit PositionControllerAddressChanged(_positionControllerAddress);
renounceOwnership();
}
/*
* @dev Add a node to the list
* @param _id Node's id
* @param _NICR Node's NICR
* @param _prevId Id of previous node for the insert position
* @param _nextId Id of next node for the insert position
*/
function insert (address _id, uint256 _NICR, address _prevId, address _nextId) external override {
IPositionManager positionManagerCached = positionManager;
_requireCallerIsPCorPM(positionManagerCached);
_insert(positionManagerCached, _id, _NICR, _prevId, _nextId);
}
function _insert(IPositionManager _positionManager, address _id, uint256 _NICR, address _prevId, address _nextId) internal {
// List must not be full
require(!isFull(), "SortedPositions: List is full");
// List must not already contain node
require(!contains(_id), "SortedPositions: List already contains the node");
// Node id must not be null
require(_id != address(0), "SortedPositions: Id cannot be zero");
// NICR must be non-zero
require(_NICR > 0, "SortedPositions: NICR must be positive");
address prevId = _prevId;
address nextId = _nextId;
if (!_validInsertPosition(_positionManager, _NICR, prevId, nextId)) {
// Sender's hint was not a valid insert position
// Use sender's hint to find a valid insert position
(prevId, nextId) = _findInsertPosition(_positionManager, _NICR, prevId, nextId);
}
data.nodes[_id].exists = true;
if (prevId == address(0) && nextId == address(0)) {
// Insert as head and tail
data.head = _id;
data.tail = _id;
} else if (prevId == address(0)) {
// Insert before `prevId` as the head
data.nodes[_id].nextId = data.head;
data.nodes[data.head].prevId = _id;
data.head = _id;
} else if (nextId == address(0)) {
// Insert after `nextId` as the tail
data.nodes[_id].prevId = data.tail;
data.nodes[data.tail].nextId = _id;
data.tail = _id;
} else {
// Insert at insert position between `prevId` and `nextId`
data.nodes[_id].nextId = nextId;
data.nodes[_id].prevId = prevId;
data.nodes[prevId].nextId = _id;
data.nodes[nextId].prevId = _id;
}
data.size = data.size + 1;
emit NodeAdded(_id, _NICR);
}
function remove(address _id) external override {
_requireCallerIsPositionManager();
_remove(_id);
}
/*
* @dev Remove a node from the list
* @param _id Node's id
*/
function _remove(address _id) internal {
// List must contain the node
require(contains(_id), "SortedPositions: List does not contain the id");
if (data.size > 1) {
// List contains more than a single node
if (_id == data.head) {
// The removed node is the head
// Set head to next node
data.head = data.nodes[_id].nextId;
// Set prev pointer of new head to null
data.nodes[data.head].prevId = address(0);
} else if (_id == data.tail) {
// The removed node is the tail
// Set tail to previous node
data.tail = data.nodes[_id].prevId;
// Set next pointer of new tail to null
data.nodes[data.tail].nextId = address(0);
} else {
// The removed node is neither the head nor the tail
// Set next pointer of previous node to the next node
data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;
// Set prev pointer of next node to the previous node
data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;
}
} else {
// List contains a single node
// Set the head and tail to null
data.head = address(0);
data.tail = address(0);
}
delete data.nodes[_id];
data.size = data.size - 1;
emit NodeRemoved(_id);
}
/*
* @dev Re-insert the node at a new position, based on its new NICR
* @param _id Node's id
* @param _newNICR Node's new NICR
* @param _prevId Id of previous node for the new insert position
* @param _nextId Id of next node for the new insert position
*/
function reInsert(address _id, uint256 _newNICR, address _prevId, address _nextId) external override {
IPositionManager positionManagerCached = positionManager;
_requireCallerIsPCorPM(positionManagerCached);
// List must contain the node
require(contains(_id), "SortedPositions: List does not contain the id");
// NICR must be non-zero
require(_newNICR > 0, "SortedPositions: NICR must be positive");
// Remove node from the list
_remove(_id);
_insert(positionManagerCached, _id, _newNICR, _prevId, _nextId);
}
/*
* @dev Checks if the list contains a node
*/
function contains(address _id) public view override returns (bool) {
return data.nodes[_id].exists;
}
/*
* @dev Checks if the list is full
*/
function isFull() public view override returns (bool) {
return data.size == data.maxSize;
}
/*
* @dev Checks if the list is empty
*/
function isEmpty() public view override returns (bool) {
return data.size == 0;
}
/*
* @dev Returns the current size of the list
*/
function getSize() external view override returns (uint256) {
return data.size;
}
/*
* @dev Returns the maximum size of the list
*/
function getMaxSize() external view override returns (uint256) {
return data.maxSize;
}
/*
* @dev Returns the first node in the list (node with the largest NICR)
*/
function getFirst() external view override returns (address) {
return data.head;
}
/*
* @dev Returns the last node in the list (node with the smallest NICR)
*/
function getLast() external view override returns (address) {
return data.tail;
}
/*
* @dev Returns the next node (with a smaller NICR) in the list for a given node
* @param _id Node's id
*/
function getNext(address _id) external view override returns (address) {
return data.nodes[_id].nextId;
}
/*
* @dev Returns the previous node (with a larger NICR) in the list for a given node
* @param _id Node's id
*/
function getPrev(address _id) external view override returns (address) {
return data.nodes[_id].prevId;
}
/*
* @dev Check if a pair of nodes is a valid insertion point for a new node with the given NICR
* @param _NICR Node's NICR
* @param _prevId Id of previous node for the insert position
* @param _nextId Id of next node for the insert position
*/
function validInsertPosition(uint256 _NICR, address _prevId, address _nextId) external view override returns (bool) {
return _validInsertPosition(positionManager, _NICR, _prevId, _nextId);
}
function _validInsertPosition(IPositionManager _positionManager, uint256 _NICR, address _prevId, address _nextId) internal view returns (bool) {
if (_prevId == address(0) && _nextId == address(0)) {
// `(null, null)` is a valid insert position if the list is empty
return isEmpty();
} else if (_prevId == address(0)) {
// `(null, _nextId)` is a valid insert position if `_nextId` is the head of the list
return data.head == _nextId && _NICR >= _positionManager.getNominalICR(_nextId);
} else if (_nextId == address(0)) {
// `(_prevId, null)` is a valid insert position if `_prevId` is the tail of the list
return data.tail == _prevId && _NICR <= _positionManager.getNominalICR(_prevId);
} else {
// `(_prevId, _nextId)` is a valid insert position if they are adjacent nodes and `_NICR` falls between the two nodes' NICRs
return data.nodes[_prevId].nextId == _nextId &&
_positionManager.getNominalICR(_prevId) >= _NICR &&
_NICR >= _positionManager.getNominalICR(_nextId);
}
}
/*
* @dev Descend the list (larger NICRs to smaller NICRs) to find a valid insert position
* @param _positionManager PositionManager contract, passed in as param to save SLOAD’s
* @param _NICR Node's NICR
* @param _startId Id of node to start descending the list from
*/
function _descendList(IPositionManager _positionManager, uint256 _NICR, address _startId) internal view returns (address, address) {
// If `_startId` is the head, check if the insert position is before the head
if (data.head == _startId && _NICR >= _positionManager.getNominalICR(_startId)) {
return (address(0), _startId);
}
address prevId = _startId;
address nextId = data.nodes[prevId].nextId;
// Descend the list until we reach the end or until we find a valid insert position
while (prevId != address(0) && !_validInsertPosition(_positionManager, _NICR, prevId, nextId)) {
prevId = data.nodes[prevId].nextId;
nextId = data.nodes[prevId].nextId;
}
return (prevId, nextId);
}
/*
* @dev Ascend the list (smaller NICRs to larger NICRs) to find a valid insert position
* @param _positionManager PositionManager contract, passed in as param to save SLOAD’s
* @param _NICR Node's NICR
* @param _startId Id of node to start ascending the list from
*/
function _ascendList(IPositionManager _positionManager, uint256 _NICR, address _startId) internal view returns (address, address) {
// If `_startId` is the tail, check if the insert position is after the tail
if (data.tail == _startId && _NICR <= _positionManager.getNominalICR(_startId)) {
return (_startId, address(0));
}
address nextId = _startId;
address prevId = data.nodes[nextId].prevId;
// Ascend the list until we reach the end or until we find a valid insertion point
while (nextId != address(0) && !_validInsertPosition(_positionManager, _NICR, prevId, nextId)) {
nextId = data.nodes[nextId].prevId;
prevId = data.nodes[nextId].prevId;
}
return (prevId, nextId);
}
/*
* @dev Find the insert position for a new node with the given NICR
* @param _NICR Node's NICR
* @param _prevId Id of previous node for the insert position
* @param _nextId Id of next node for the insert position
*/
function findInsertPosition(uint256 _NICR, address _prevId, address _nextId) external view override returns (address, address) {
return _findInsertPosition(positionManager, _NICR, _prevId, _nextId);
}
function _findInsertPosition(IPositionManager _positionManager, uint256 _NICR, address _prevId, address _nextId) internal view returns (address, address) {
address prevId = _prevId;
address nextId = _nextId;
if (prevId != address(0)) {
if (!contains(prevId) || _NICR > _positionManager.getNominalICR(prevId)) {
// `prevId` does not exist anymore or now has a smaller NICR than the given NICR
prevId = address(0);
}
}
if (nextId != address(0)) {
if (!contains(nextId) || _NICR < _positionManager.getNominalICR(nextId)) {
// `nextId` does not exist anymore or now has a larger NICR than the given NICR
nextId = address(0);
}
}
if (prevId == address(0) && nextId == address(0)) {
// No hint - descend list starting from head
return _descendList(_positionManager, _NICR, data.head);
} else if (prevId == address(0)) {
// No `prevId` for hint - ascend list starting from `nextId`
return _ascendList(_positionManager, _NICR, nextId);
} else if (nextId == address(0)) {
// No `nextId` for hint - descend list starting from `prevId`
return _descendList(_positionManager, _NICR, prevId);
} else {
// Descend list starting from `prevId`
return _descendList(_positionManager, _NICR, prevId);
}
}
// --- 'require' functions ---
function _requireCallerIsPositionManager() internal view {
require(msg.sender == address(positionManager), "SortedPositions: Caller is not the PositionManager");
}
function _requireCallerIsPCorPM(IPositionManager _positionManager) internal view {
require(msg.sender == positionControllerAddress || msg.sender == address(_positionManager),
"SortedPositions: Caller is neither PosCtrlr nor PosManager");
}
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 1
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}
}}
Submitted on: 2025-10-28 12:41:33
Comments
Log in to comment.
No comments yet.