// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract BaseMath {
uint256 constant public DECIMAL_PRECISION = 1e18;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract CheckContract {
/**
* Check that the account is an already deployed non-destroyed contract.
* See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L12
*/
function checkContract(address _account) internal view {
require(_account != address(0), "Account cannot be zero address");
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(_account) }
require(size > 0, "Account code size cannot be zero");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IPool.sol";
interface IActivePool is IPool {
// --- Events ---
event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event ActivePoolTHUSDDebtUpdated(uint256 _THUSDDebt);
event ActivePoolCollateralBalanceUpdated(uint256 _collateral);
event CollateralAddressChanged(address _newCollateralAddress);
event CollSurplusPoolAddressChanged(address _newCollSurplusPoolAddress);
// --- Functions ---
function sendCollateral(address _account, uint256 _amount) external;
function updateCollateralBalance(uint256 _amount) external;
function collateralAddress() external view returns(address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface ICollSurplusPool {
// --- Events ---
event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event ActivePoolAddressChanged(address _newActivePoolAddress);
event CollateralAddressChanged(address _newCollateralAddress);
event CollBalanceUpdated(address indexed _account, uint256 _newBalance);
event CollateralSent(address _to, uint256 _amount);
// --- Contract setters ---
function setAddresses(
address _borrowerOperationsAddress,
address _troveManagerAddress,
address _activePoolAddress,
address _collateralAddress
) external;
function getCollateralBalance() external view returns (uint);
function getCollateral(address _account) external view returns (uint);
function accountSurplus(address _account, uint256 _amount) external;
function claimColl(address _account) external;
function updateCollateralBalance(uint256 _amount) external;
function collateralAddress() external view returns(address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IPool.sol";
interface IDefaultPool is IPool {
// --- Events ---
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event DefaultPoolTHUSDDebtUpdated(uint256 _THUSDDebt);
event DefaultPoolCollateralBalanceUpdated(uint256 _collateral);
event CollateralAddressChanged(address _newCollateralAddress);
// --- Functions ---
function sendCollateralToActivePool(uint256 _amount) external;
function updateCollateralBalance(uint256 _amount) external;
function collateralAddress() external view returns(address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @dev Interface of the ERC2612 standard as defined in the EIP.
*
* Adds the {permit} method, which can be used to change one's
* {IERC20-allowance} without having to send a transaction, by signing a
* message. This allows users to spend tokens without having to hold Ether.
*
* See https://eips.ethereum.org/EIPS/eip-2612.
*
* Code adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237/
*/
interface IERC2612 {
/**
* @dev Sets `amount` 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:
*
* - `owner` cannot be the zero address.
* - `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 amount,
uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
/**
* @dev Returns the current ERC2612 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.
*
* `owner` can limit the time a Permit is valid for by setting `deadline` to
* a value in the near future. The deadline argument can be set to uint(-1) to
* create Permits that effectively never expire.
*/
function nonces(address owner) external view returns (uint256);
function version() external view returns (string memory);
function permitTypeHash() external view returns (bytes32);
function domainSeparator() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IGasPool {
// --- Events ---
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event THUSDTokenAddressChanged(address _thusdTokenAddress);
// --- Functions ---
function sendTHUSD(address _account, uint256 _amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IPriceFeed.sol";
interface ILiquityBase {
function priceFeed() external view returns (IPriceFeed);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./ITHUSDToken.sol";
interface IPCV {
// --- Events --
event THUSDTokenAddressSet(address _thusdTokenAddress);
event BorrowerOperationsAddressSet(address _borrowerOperationsAddress);
event CollateralAddressSet(address _collateralAddress);
event BAMMAddressSet(address _bammAddress);
event RolesSet(address _council, address _treasury);
event BAMMDeposit(uint256 _thusdAmount);
event BAMMWithdraw(uint256 _numShares);
event THUSDWithdraw(address _recipient, uint256 _thusdAmount);
event CollateralWithdraw(address _recipient, uint256 _collateralAmount);
event PCVDebtPaid(uint256 _paidDebt);
event RecipientAdded(address _recipient);
event RecipientRemoved(address _recipient);
// --- Functions ---
function debtToPay() external returns(uint256);
function payDebt(uint256 _thusdToBurn) external;
function setAddresses(
address _thusdTokenAddress,
address _borrowerOperations,
address payable _bammAddress,
address _collateralERC20
) external;
function initialize() external;
function depositToBAMM(uint256 _thusdAmount) external;
function withdrawFromBAMM(uint256 _numShares) external;
function withdrawTHUSD(address _recipient, uint256 _thusdAmount) external;
function withdrawCollateral(address _recipient, uint256 _collateralAmount) external;
function addRecipientToWhitelist(address _recipient) external;
function addRecipientsToWhitelist(address[] calldata _recipients) external;
function removeRecipientFromWhitelist(address _recipient) external;
function removeRecipientsFromWhitelist(address[] calldata _recipients) external;
function startChangingRoles(address _council, address _treasury) external;
function cancelChangingRoles() external;
function finalizeChangingRoles() external;
function collateralERC20() external view returns(IERC20);
function thusdToken() external view returns(ITHUSDToken);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Common interface for the Pools.
interface IPool {
// --- Events ---
event CollateralBalanceUpdated(uint256 _newBalance);
event THUSDBalanceUpdated(uint256 _newBalance);
event ActivePoolAddressChanged(address _newActivePoolAddress);
event DefaultPoolAddressChanged(address _newDefaultPoolAddress);
event StabilityPoolAddressChanged(address _newStabilityPoolAddress);
event CollateralSent(address _to, uint256 _amount);
// --- Functions ---
function getCollateralBalance() external view returns (uint);
function getTHUSDDebt() external view returns (uint);
function increaseTHUSDDebt(uint256 _amount) external;
function decreaseTHUSDDebt(uint256 _amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IPriceFeed {
// --- Events ---
event LastGoodPriceUpdated(uint256 _lastGoodPrice);
// --- Function ---
function fetchPrice() external returns (uint);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// Common interface for the SortedTroves Doubly Linked List.
interface ISortedTroves {
// --- Events ---
event SortedTrovesAddressChanged(address _sortedDoublyLLAddress);
event BorrowerOperationsAddressChanged(address _borrowerOperationsAddress);
event NodeAdded(address _id, uint256 _NICR);
event NodeRemoved(address _id);
// --- Functions ---
function setParams(uint256 _size, address _TroveManagerAddress, address _borrowerOperationsAddress) external;
function insert(address _id, uint256 _ICR, address _prevId, address _nextId) external;
function remove(address _id) external;
function reInsert(address _id, uint256 _newICR, address _prevId, address _nextId) external;
function contains(address _id) external view returns (bool);
function isFull() external view returns (bool);
function isEmpty() external view returns (bool);
function getSize() external view returns (uint256);
function getMaxSize() external view returns (uint256);
function getFirst() external view returns (address);
function getLast() external view returns (address);
function getNext(address _id) external view returns (address);
function getPrev(address _id) external view returns (address);
function validInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (bool);
function findInsertPosition(uint256 _ICR, address _prevId, address _nextId) external view returns (address, address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/*
* The Stability Pool holds THUSD tokens deposited by Stability Pool depositors.
*
* When a trove is liquidated, then depending on system conditions, some of its THUSD debt gets offset with
* THUSD in the Stability Pool: that is, the offset debt evaporates, and an equal amount of THUSD tokens in the Stability Pool is burned.
*
* Thus, a liquidation causes each depositor to receive a THUSD loss, in proportion to their deposit as a share of total deposits.
* They also receive an collateral gain, as the collateral of the liquidated trove is distributed among Stability depositors,
* in the same proportion.
*
* When a liquidation occurs, it depletes every deposit by the same fraction: for example, a liquidation that depletes 40%
* of the total THUSD in the Stability Pool, depletes 40% of each deposit.
*
* A deposit that has experienced a series of liquidations is termed a "compounded deposit": each liquidation depletes the deposit,
* multiplying it by some factor in range ]0,1[
*
* Please see the implementation spec in the proof document, which closely follows on from the compounded deposit / collateral gain derivations:
* https://github.com/liquity/liquity/blob/master/papers/Scalable_Reward_Distribution_with_Compounding_Stakes.pdf
*
*/
interface IStabilityPool {
// --- Events ---
event StabilityPoolCollateralBalanceUpdated(uint256 _newBalance);
event StabilityPoolTHUSDBalanceUpdated(uint256 _newBalance);
event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);
event TroveManagerAddressChanged(address _newTroveManagerAddress);
event ActivePoolAddressChanged(address _newActivePoolAddress);
event DefaultPoolAddressChanged(address _newDefaultPoolAddress);
event THUSDTokenAddressChanged(address _newTHUSDTokenAddress);
event SortedTrovesAddressChanged(address _newSortedTrovesAddress);
event PriceFeedAddressChanged(address _newPriceFeedAddress);
event CollateralAddressChanged(address _newCollateralAddress);
event P_Updated(uint256 _P);
event S_Updated(uint256 _S, uint128 _epoch, uint128 _scale);
event EpochUpdated(uint128 _currentEpoch);
event ScaleUpdated(uint128 _currentScale);
event DepositSnapshotUpdated(address indexed _depositor, uint256 _P, uint256 _S);
event UserDepositChanged(address indexed _depositor, uint256 _newDeposit);
event CollateralGainWithdrawn(address indexed _depositor, uint256 _collateral, uint256 _THUSDLoss);
event CollateralSent(address _to, uint256 _amount);
// --- Functions ---
/*
* Called only once on init, to set addresses of other Liquity contracts
* Callable only by owner, renounces ownership at the end
*/
function setAddresses(
address _borrowerOperationsAddress,
address _troveManagerAddress,
address _activePoolAddress,
address _thusdTokenAddress,
address _sortedTrovesAddress,
address _priceFeedAddress,
address _collateralAddress
) external;
/*
* Initial checks:
* - _amount is not zero
* ---
* - Sends depositor's accumulated gains (collateral) to depositor
*/
function provideToSP(uint256 _amount) external;
/*
* Initial checks:
* - _amount is zero or there are no under collateralized troves left in the system
* - User has a non zero deposit
* ---
* - Sends all depositor's accumulated gains (collateral) to depositor
* - Decreases deposit stake, and takes new snapshot.
*
* If _amount > userDeposit, the user withdraws all of their compounded deposit.
*/
function withdrawFromSP(uint256 _amount) external;
/*
* Initial checks:
* - User has a non zero deposit
* - User has an open trove
* - User has some collateral gain
* ---
* - Transfers the depositor's entire collateral gain from the Stability Pool to the caller's trove
* - Leaves their compounded deposit in the Stability Pool
* - Updates snapshots for deposit
*/
function withdrawCollateralGainToTrove(address _upperHint, address _lowerHint) external;
/*
* Initial checks:
* - Caller is TroveManager
* ---
* Cancels out the specified debt against the THUSD contained in the Stability Pool (as far as possible)
* and transfers the Trove's collateral from ActivePool to StabilityPool.
* Only called by liquidation functions in the TroveManager.
*/
function offset(uint256 _debt, uint256 _coll) external;
/*
* Returns the total amount of collateral held by the pool, accounted in an internal variable instead of `balance`,
* to exclude edge cases like collateral received from a self-destruct.
*/
function getCollateralBalance() external view returns (uint);
/*
* Returns THUSD held in the pool. Changes when users deposit/withdraw, and when Trove debt is offset.
*/
function getTotalTHUSDDeposits() external view returns (uint);
/*
* Calculates the collateral gain earned by the deposit since its last snapshots were taken.
*/
function getDepositorCollateralGain(address _depositor) external view returns (uint);
/*
* Return the user's compounded deposit.
*/
function getCompoundedTHUSDDeposit(address _depositor) external view returns (uint);
/*
* Only callable by Active Pool, updates ERC20 tokens recieved
*/
function updateCollateralBalance(uint256 _amount) external;
/*
* Fallback function
* Only callable by Active Pool, it just accounts for ETH received
* receive() external payable;
*/
function collateralAddress() external view returns(address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../Dependencies/IERC2612.sol";
interface ITHUSDToken is IERC20Metadata, IERC2612 {
// --- Events ---
event TroveManagerAddressAdded(address _troveManagerAddress);
event StabilityPoolAddressAdded(address _newStabilityPoolAddress);
event BorrowerOperationsAddressAdded(address _newBorrowerOperationsAddress);
event THUSDTokenBalanceUpdated(address _user, uint256 _amount);
// --- Functions ---
function mintList(address contractAddress) external view returns (bool);
function burnList(address contractAddress) external view returns (bool);
function mint(address _account, uint256 _amount) external;
function burn(address _account, uint256 _amount) external;
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./ILiquityBase.sol";
import "./IStabilityPool.sol";
import "./ITHUSDToken.sol";
import "./IPCV.sol";
// Common interface for the Trove Manager.
interface ITroveManager is ILiquityBase {
enum Status {
nonExistent,
active,
closedByOwner,
closedByLiquidation,
closedByRedemption
}
// --- Events ---
event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress);
event PriceFeedAddressChanged(address _newPriceFeedAddress);
event THUSDTokenAddressChanged(address _newTHUSDTokenAddress);
event ActivePoolAddressChanged(address _activePoolAddress);
event DefaultPoolAddressChanged(address _defaultPoolAddress);
event StabilityPoolAddressChanged(address _stabilityPoolAddress);
event GasPoolAddressChanged(address _gasPoolAddress);
event CollSurplusPoolAddressChanged(address _collSurplusPoolAddress);
event SortedTrovesAddressChanged(address _sortedTrovesAddress);
event PCVAddressChanged(address _pcvAddress);
event Liquidation(uint256 _liquidatedDebt, uint256 _liquidatedColl, uint256 _collGasCompensation, uint256 _THUSDGasCompensation);
event Redemption(uint256 _attemptedTHUSDAmount, uint256 _actualTHUSDAmount, uint256 _collateralSent, uint256 _collateralFee);
event TroveUpdated(address indexed _borrower, uint256 _debt, uint256 _coll, uint256 stake, uint8 operation);
event TroveLiquidated(address indexed _borrower, uint256 _debt, uint256 _coll, uint8 operation);
event BaseRateUpdated(uint256 _baseRate);
event LastFeeOpTimeUpdated(uint256 _lastFeeOpTime);
event TotalStakesUpdated(uint256 _newTotalStakes);
event SystemSnapshotsUpdated(uint256 _totalStakesSnapshot, uint256 _totalCollateralSnapshot);
event LTermsUpdated(uint256 _L_Collateral, uint256 _L_THUSDDebt);
event TroveSnapshotsUpdated(uint256 _L_Collateral, uint256 _L_THUSDDebt);
event TroveIndexUpdated(address _borrower, uint256 _newIndex);
// --- Functions ---
function setAddresses(
address _borrowerOperationsAddress,
address _activePoolAddress,
address _defaultPoolAddress,
address _stabilityPoolAddress,
address _gasPoolAddress,
address _collSurplusPoolAddress,
address _priceFeedAddress,
address _thusdTokenAddress,
address _sortedTrovesAddress,
address _pcvAddress
) external;
function stabilityPool() external view returns (IStabilityPool);
function thusdToken() external view returns (ITHUSDToken);
function pcv() external view returns (IPCV);
function getTroveOwnersCount() external view returns (uint);
function getTroveFromTroveOwnersArray(uint256 _index) external view returns (address);
function getNominalICR(address _borrower) external view returns (uint);
function getCurrentICR(address _borrower, uint256 _price) external view returns (uint);
function liquidate(address _borrower) external;
function liquidateTroves(uint256 _n) external;
function batchLiquidateTroves(address[] calldata _troveArray) external;
function redeemCollateral(
uint256 _THUSDAmount,
address _firstRedemptionHint,
address _upperPartialRedemptionHint,
address _lowerPartialRedemptionHint,
uint256 _partialRedemptionHintNICR,
uint256 _maxIterations,
uint256 _maxFee
) external;
function updateStakeAndTotalStakes(address _borrower) external returns (uint);
function updateTroveRewardSnapshots(address _borrower) external;
function addTroveOwnerToArray(address _borrower) external returns (uint256 index);
function applyPendingRewards(address _borrower) external;
function getPendingCollateralReward(address _borrower) external view returns (uint);
function getPendingTHUSDDebtReward(address _borrower) external view returns (uint);
function hasPendingRewards(address _borrower) external view returns (bool);
function getEntireDebtAndColl(address _borrower) external view returns (
uint256 debt,
uint256 coll,
uint256 pendingTHUSDDebtReward,
uint256 pendingCollateralReward
);
function closeTrove(address _borrower) external;
function removeStake(address _borrower) external;
function getRedemptionRate() external view returns (uint);
function getRedemptionRateWithDecay() external view returns (uint);
function getRedemptionFeeWithDecay(uint256 _collateralDrawn) external view returns (uint);
function getBorrowingRate() external view returns (uint);
function getBorrowingRateWithDecay() external view returns (uint);
function getBorrowingFee(uint256 THUSDDebt) external view returns (uint);
function getBorrowingFeeWithDecay(uint256 _THUSDDebt) external view returns (uint);
function decayBaseRateFromBorrowing() external;
function getTroveStatus(address _borrower) external view returns (Status);
function getTroveStake(address _borrower) external view returns (uint);
function getTroveDebt(address _borrower) external view returns (uint);
function getTroveColl(address _borrower) external view returns (uint);
function setTroveStatus(address _borrower, Status _status) external;
function increaseTroveColl(address _borrower, uint256 _collIncrease) external returns (uint);
function decreaseTroveColl(address _borrower, uint256 _collDecrease) external returns (uint);
function increaseTroveDebt(address _borrower, uint256 _debtIncrease) external returns (uint);
function decreaseTroveDebt(address _borrower, uint256 _collDecrease) external returns (uint);
function getTCR(uint256 _price) external view returns (uint);
function checkRecoveryMode(uint256 _price) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./BaseMath.sol";
import "./LiquityMath.sol";
import "../Interfaces/IActivePool.sol";
import "../Interfaces/IDefaultPool.sol";
import "../Interfaces/IPriceFeed.sol";
import "../Interfaces/ILiquityBase.sol";
/*
* Base contract for TroveManager, BorrowerOperations and StabilityPool. Contains global system constants and
* common functions.
*/
contract LiquityBase is BaseMath, ILiquityBase {
uint256 constant public _100pct = 1e18; // 1e18 == 100%
// Minimum collateral ratio for individual troves
uint256 constant public MCR = 1.1e18; // 110%
// Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered.
uint256 constant public CCR = 1.5e18; // 150%
// Amount of THUSD to be locked in gas pool on opening troves
uint256 constant public THUSD_GAS_COMPENSATION = 200e18;
// Minimum amount of net THUSD debt a trove must have
uint256 constant public MIN_NET_DEBT = 1800e18;
// uint256 constant public MIN_NET_DEBT = 0;
uint256 constant public PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%
uint256 constant public BORROWING_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%
IActivePool public activePool;
IDefaultPool public defaultPool;
IPriceFeed public override priceFeed;
// --- Gas compensation functions ---
// Returns the composite debt (drawn debt + gas compensation) of a trove, for the purpose of ICR calculation
function _getCompositeDebt(uint256 _debt) internal pure returns (uint) {
return _debt + THUSD_GAS_COMPENSATION;
}
function _getNetDebt(uint256 _debt) internal pure returns (uint) {
return _debt - THUSD_GAS_COMPENSATION;
}
// Return the amount of collateral to be drawn from a trove's collateral and sent as gas compensation.
function _getCollGasCompensation(uint256 _entireColl) internal pure returns (uint) {
return _entireColl / PERCENT_DIVISOR;
}
function getEntireSystemColl() public view returns (uint256 entireSystemColl) {
uint256 activeColl = activePool.getCollateralBalance();
uint256 liquidatedColl = defaultPool.getCollateralBalance();
return activeColl + liquidatedColl;
}
function getEntireSystemDebt() public view returns (uint256 entireSystemDebt) {
uint256 activeDebt = activePool.getTHUSDDebt();
uint256 closedDebt = defaultPool.getTHUSDDebt();
return activeDebt + closedDebt;
}
function _getTCR(uint256 _price) internal view returns (uint256 TCR) {
uint256 entireSystemColl = getEntireSystemColl();
uint256 entireSystemDebt = getEntireSystemDebt();
TCR = LiquityMath._computeCR(entireSystemColl, entireSystemDebt, _price);
return TCR;
}
function _checkRecoveryMode(uint256 _price) internal view returns (bool) {
uint256 TCR = _getTCR(_price);
return TCR < CCR;
}
function _requireUserAcceptsFee(uint256 _fee, uint256 _amount, uint256 _maxFeePercentage) internal pure {
uint256 feePercentage = _fee * DECIMAL_PRECISION / _amount;
require(feePercentage <= _maxFeePercentage, "Fee exceeded provided maximum");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
library LiquityMath {
uint256 internal constant DECIMAL_PRECISION = 1e18;
/* Precision for Nominal ICR (independent of price). Rationale for the value:
*
* - Making it “too high” could lead to overflows.
* - Making it “too low” could lead to an ICR equal to zero, due to truncation from Solidity floor division.
*
* This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 ETH,
* and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.
*
*/
uint256 internal constant NICR_PRECISION = 1e20;
function _min(uint256 _a, uint256 _b) internal pure returns (uint) {
return (_a < _b) ? _a : _b;
}
function _max(uint256 _a, uint256 _b) internal pure returns (uint) {
return (_a >= _b) ? _a : _b;
}
/*
* Multiply two decimal numbers and use normal rounding rules:
* -round product up if 19'th mantissa digit >= 5
* -round product down if 19'th mantissa digit < 5
*
* Used only inside the exponentiation, _decPow().
*/
function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {
uint256 prod_xy = x * y;
decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;
}
/*
* _decPow: Exponentiation function for 18-digit decimal base, and integer exponent n.
*
* Uses the efficient "exponentiation by squaring" algorithm. O(log(n)) complexity.
*
* Called by one function that represent time in units of minutes:
* 1) TroveManager._calcDecayedBaseRate
*
* The exponent is capped to avoid reverting due to overflow. The cap 525600000 equals
* "minutes in 1000 years": 60 * 24 * 365 * 1000
*
* If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be
* negligibly different from just passing the cap, since:
*
* In function 1), the decayed base rate will be 0 for 1000 years or > 1000 years
* In function 2), the difference in tokens issued at 1000 years and any time > 1000 years, will be negligible
*/
function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint) {
if (_minutes > 525600000) {_minutes = 525600000;} // cap to avoid overflow
if (_minutes == 0) {return DECIMAL_PRECISION;}
uint256 y = DECIMAL_PRECISION;
uint256 x = _base;
uint256 n = _minutes;
// Exponentiation-by-squaring
while (n > 1) {
if (n % 2 == 0) {
x = decMul(x, x);
n = n / 2;
} else { // if (n % 2 != 0)
y = decMul(x, y);
x = decMul(x, x);
n = (n - 1) / 2;
}
}
return decMul(x, y);
}
function _getAbsoluteDifference(uint256 _a, uint256 _b) internal pure returns (uint) {
return (_a >= _b) ? _a - _b : _b - _a;
}
function _computeNominalCR(uint256 _coll, uint256 _debt) internal pure returns (uint) {
if (_debt > 0) {
return _coll * NICR_PRECISION / _debt;
}
// Return the maximal value for uint256 if the Trove has a debt of 0. Represents "infinite" CR.
else { // if (_debt == 0)
return type(uint256).max;
}
}
function _computeCR(uint256 _coll, uint256 _debt, uint256 _price) internal pure returns (uint) {
if (_debt > 0) {
uint256 newCollRatio = _coll * _price / _debt;
return newCollRatio;
}
// Return the maximal value for uint256 if the Trove has a debt of 0. Represents "infinite" CR.
else { // if (_debt == 0)
return type(uint256).max;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* Based on OpenZeppelin's Ownable contract:
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.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.
*
* 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.
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () {
_owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*
* NOTE: This function is not safe, as it doesn’t check owner is calling it.
* Make sure you check it before calling it.
*/
function _renounceOwnership() internal {
emit OwnershipTransferred(_owner, address(0));
_owner = 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);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./Interfaces/ITroveManager.sol";
import "./Interfaces/IStabilityPool.sol";
import "./Interfaces/ICollSurplusPool.sol";
import "./Interfaces/IGasPool.sol";
import "./Interfaces/ITHUSDToken.sol";
import "./Interfaces/ISortedTroves.sol";
import "./Interfaces/IPCV.sol";
import "./Dependencies/LiquityBase.sol";
import "./Dependencies/Ownable.sol";
import "./Dependencies/CheckContract.sol";
contract TroveManager is LiquityBase, Ownable, CheckContract, ITroveManager {
string constant public NAME = "TroveManager";
// --- Connected contract declarations ---
address public borrowerOperationsAddress;
IStabilityPool public override stabilityPool;
address gasPoolAddress;
ICollSurplusPool collSurplusPool;
ITHUSDToken public override thusdToken;
IPCV public override pcv;
// A doubly linked list of Troves, sorted by their sorted by their collateral ratios
ISortedTroves public sortedTroves;
// --- Data structures ---
/*
* Half-life of 12h. 12h = 720 min
* (1/2) = d^720 => d = (1/2)^(1/720)
*/
uint256 constant public MINUTE_DECAY_FACTOR = 999037758833783000;
uint256 constant public REDEMPTION_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%
uint256 constant public MAX_BORROWING_FEE = DECIMAL_PRECISION / 100 * 5; // 5%
/*
* BETA: 18 digit decimal. Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a redemption.
* Corresponds to (1 / ALPHA) in the white paper.
*/
uint256 constant public BETA = 2;
uint256 public baseRate;
// The timestamp of the latest fee operation (redemption or new THUSD issuance)
uint256 public lastFeeOperationTime;
// Store the necessary data for a trove
struct Trove {
uint256 debt;
uint256 coll;
uint256 stake;
Status status;
uint128 arrayIndex;
}
mapping (address => Trove) public Troves;
uint256 public totalStakes;
// Snapshot of the value of totalStakes, taken immediately after the latest liquidation
uint256 public totalStakesSnapshot;
// Snapshot of the total collateral across the ActivePool and DefaultPool, immediately after the latest liquidation.
uint256 public totalCollateralSnapshot;
/*
* L_Collateral and L_THUSDDebt track the sums of accumulated liquidation rewards per unit staked. During its lifetime, each stake earns:
*
* An collateral gain of ( stake * [L_Collateral - L_Collateral(0)] )
* A THUSDDebt increase of ( stake * [L_THUSDDebt - L_THUSDDebt(0)] )
*
* Where L_Collateral(0) and L_THUSDDebt(0) are snapshots of L_Collateral and L_THUSDDebt for the active Trove taken at the instant the stake was made
*/
uint256 public L_Collateral;
uint256 public L_THUSDDebt;
// Map addresses with active troves to their RewardSnapshot
mapping (address => RewardSnapshot) public rewardSnapshots;
// Object containing the collateral and THUSD snapshots for a given active trove
struct RewardSnapshot { uint256 collateral; uint256 THUSDDebt;}
// Array of all active trove addresses - used to to compute an approximate hint off-chain, for the sorted list insertion
address[] public TroveOwners;
// Error trackers for the trove redistribution calculation
uint256 public lastCollateralError_Redistribution;
uint256 public lastTHUSDDebtError_Redistribution;
/*
* --- Variable container structs for liquidations ---
*
* These structs are used to hold, return and assign variables inside the liquidation functions,
* in order to avoid the error: "CompilerError: Stack too deep".
**/
struct LocalVariables_OuterLiquidationFunction {
uint256 price;
uint256 THUSDInStabPool;
bool recoveryModeAtStart;
uint256 liquidatedDebt;
uint256 liquidatedColl;
}
struct LocalVariables_InnerSingleLiquidateFunction {
uint256 collToLiquidate;
uint256 pendingDebtReward;
uint256 pendingCollReward;
}
struct LocalVariables_LiquidationSequence {
uint256 remainingTHUSDInStabPool;
uint256 i;
uint256 ICR;
address user;
bool backToNormalMode;
uint256 entireSystemDebt;
uint256 entireSystemColl;
}
struct LiquidationValues {
uint256 entireTroveDebt;
uint256 entireTroveColl;
uint256 collGasCompensation;
uint256 THUSDGasCompensation;
uint256 debtToOffset;
uint256 collToSendToSP;
uint256 debtToRedistribute;
uint256 collToRedistribute;
uint256 collSurplus;
}
struct LiquidationTotals {
uint256 totalCollInSequence;
uint256 totalDebtInSequence;
uint256 totalCollGasCompensation;
uint256 totalTHUSDGasCompensation;
uint256 totalDebtToOffset;
uint256 totalCollToSendToSP;
uint256 totalDebtToRedistribute;
uint256 totalCollToRedistribute;
uint256 totalCollSurplus;
}
struct ContractsCache {
IActivePool activePool;
IDefaultPool defaultPool;
ITHUSDToken thusdToken;
IPCV pcv;
ISortedTroves sortedTroves;
ICollSurplusPool collSurplusPool;
address gasPoolAddress;
}
// --- Variable container structs for redemptions ---
struct RedemptionTotals {
uint256 remainingTHUSD;
uint256 totalTHUSDToRedeem;
uint256 totalCollateralDrawn;
uint256 collateralFee;
uint256 collateralToSendToRedeemer;
uint256 decayedBaseRate;
uint256 price;
uint256 totalTHUSDDebtAtStart;
}
struct SingleRedemptionValues {
uint256 THUSDLot;
uint256 collateralLot;
bool cancelledPartial;
}
// --- Events ---
event TroveUpdated(address indexed _borrower, uint256 _debt, uint256 _coll, uint256 _stake, TroveManagerOperation _operation);
event TroveLiquidated(address indexed _borrower, uint256 _debt, uint256 _coll, TroveManagerOperation _operation);
enum TroveManagerOperation {
applyPendingRewards,
liquidateInNormalMode,
liquidateInRecoveryMode,
redeemCollateral
}
// --- Dependency setter ---
function setAddresses(
address _borrowerOperationsAddress,
address _activePoolAddress,
address _defaultPoolAddress,
address _stabilityPoolAddress,
address _gasPoolAddress,
address _collSurplusPoolAddress,
address _priceFeedAddress,
address _thusdTokenAddress,
address _sortedTrovesAddress,
address _pcvAddress
)
external
override
onlyOwner
{
checkContract(_borrowerOperationsAddress);
checkContract(_activePoolAddress);
checkContract(_defaultPoolAddress);
checkContract(_stabilityPoolAddress);
checkContract(_gasPoolAddress);
checkContract(_collSurplusPoolAddress);
checkContract(_priceFeedAddress);
checkContract(_thusdTokenAddress);
checkContract(_sortedTrovesAddress);
checkContract(_pcvAddress);
borrowerOperationsAddress = _borrowerOperationsAddress;
activePool = IActivePool(_activePoolAddress);
defaultPool = IDefaultPool(_defaultPoolAddress);
stabilityPool = IStabilityPool(_stabilityPoolAddress);
gasPoolAddress = _gasPoolAddress;
collSurplusPool = ICollSurplusPool(_collSurplusPoolAddress);
priceFeed = IPriceFeed(_priceFeedAddress);
thusdToken = ITHUSDToken(_thusdTokenAddress);
sortedTroves = ISortedTroves(_sortedTrovesAddress);
pcv = IPCV(_pcvAddress);
emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress);
emit ActivePoolAddressChanged(_activePoolAddress);
emit DefaultPoolAddressChanged(_defaultPoolAddress);
emit StabilityPoolAddressChanged(_stabilityPoolAddress);
emit GasPoolAddressChanged(_gasPoolAddress);
emit CollSurplusPoolAddressChanged(_collSurplusPoolAddress);
emit PriceFeedAddressChanged(_priceFeedAddress);
emit THUSDTokenAddressChanged(_thusdTokenAddress);
emit SortedTrovesAddressChanged(_sortedTrovesAddress);
emit PCVAddressChanged(_pcvAddress);
_renounceOwnership();
}
// --- Getters ---
function getTroveOwnersCount() external view override returns (uint) {
return TroveOwners.length;
}
function getTroveFromTroveOwnersArray(uint256 _index) external view override returns (address) {
return TroveOwners[_index];
}
// --- Trove Liquidation functions ---
// Single liquidation function. Closes the trove if its ICR is lower than the minimum collateral ratio.
function liquidate(address _borrower) external override {
_requireTroveIsActive(_borrower);
address[] memory borrowers = new address[](1);
borrowers[0] = _borrower;
batchLiquidateTroves(borrowers);
}
// --- Inner single liquidation functions ---
// Liquidate one trove, in Normal Mode.
function _liquidateNormalMode(
IActivePool _activePool,
IDefaultPool _defaultPool,
address _borrower,
uint256 _THUSDInStabPool
)
internal
returns (LiquidationValues memory singleLiquidation)
{
LocalVariables_InnerSingleLiquidateFunction memory vars;
(singleLiquidation.entireTroveDebt,
singleLiquidation.entireTroveColl,
vars.pendingDebtReward,
vars.pendingCollReward) = getEntireDebtAndColl(_borrower);
_movePendingTroveRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
_removeStake(_borrower);
singleLiquidation.collGasCompensation = _getCollGasCompensation(singleLiquidation.entireTroveColl);
singleLiquidation.THUSDGasCompensation = THUSD_GAS_COMPENSATION;
uint256 collToLiquidate = singleLiquidation.entireTroveColl - singleLiquidation.collGasCompensation;
(singleLiquidation.debtToOffset,
singleLiquidation.collToSendToSP,
singleLiquidation.debtToRedistribute,
singleLiquidation.collToRedistribute) = _getOffsetAndRedistributionVals(singleLiquidation.entireTroveDebt, collToLiquidate, _THUSDInStabPool);
_closeTrove(_borrower, Status.closedByLiquidation);
emit TroveLiquidated(_borrower, singleLiquidation.entireTroveDebt, singleLiquidation.entireTroveColl, TroveManagerOperation.liquidateInNormalMode);
emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInNormalMode);
return singleLiquidation;
}
// Liquidate one trove, in Recovery Mode.
function _liquidateRecoveryMode(
IActivePool _activePool,
IDefaultPool _defaultPool,
address _borrower,
uint256 _ICR,
uint256 _THUSDInStabPool,
uint256 _TCR,
uint256 _price
)
internal
returns (LiquidationValues memory singleLiquidation)
{
LocalVariables_InnerSingleLiquidateFunction memory vars;
if (TroveOwners.length <= 1) {return singleLiquidation;} // don't liquidate if last trove
(singleLiquidation.entireTroveDebt,
singleLiquidation.entireTroveColl,
vars.pendingDebtReward,
vars.pendingCollReward) = getEntireDebtAndColl(_borrower);
singleLiquidation.collGasCompensation = _getCollGasCompensation(singleLiquidation.entireTroveColl);
singleLiquidation.THUSDGasCompensation = THUSD_GAS_COMPENSATION;
vars.collToLiquidate = singleLiquidation.entireTroveColl - singleLiquidation.collGasCompensation;
// If ICR <= 100%, purely redistribute the Trove across all active Troves
if (_ICR <= _100pct) {
_movePendingTroveRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
_removeStake(_borrower);
singleLiquidation.debtToOffset = 0;
singleLiquidation.collToSendToSP = 0;
singleLiquidation.debtToRedistribute = singleLiquidation.entireTroveDebt;
singleLiquidation.collToRedistribute = vars.collToLiquidate;
_closeTrove(_borrower, Status.closedByLiquidation);
emit TroveLiquidated(_borrower, singleLiquidation.entireTroveDebt, singleLiquidation.entireTroveColl, TroveManagerOperation.liquidateInRecoveryMode);
emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInRecoveryMode);
// If 100% < ICR < MCR, offset as much as possible, and redistribute the remainder
} else if ((_ICR > _100pct) && (_ICR < MCR)) {
_movePendingTroveRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
_removeStake(_borrower);
(singleLiquidation.debtToOffset,
singleLiquidation.collToSendToSP,
singleLiquidation.debtToRedistribute,
singleLiquidation.collToRedistribute) = _getOffsetAndRedistributionVals(singleLiquidation.entireTroveDebt, vars.collToLiquidate, _THUSDInStabPool);
_closeTrove(_borrower, Status.closedByLiquidation);
emit TroveLiquidated(_borrower, singleLiquidation.entireTroveDebt, singleLiquidation.entireTroveColl, TroveManagerOperation.liquidateInRecoveryMode);
emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInRecoveryMode);
/*
* If 110% <= ICR < current TCR (accounting for the preceding liquidations in the current sequence)
* and there is THUSD in the Stability Pool, only offset, with no redistribution,
* but at a capped rate of 1.1 and only if the whole debt can be liquidated.
* The remainder due to the capped rate will be claimable as collateral surplus.
*/
} else if ((_ICR >= MCR) && (_ICR < _TCR) && (singleLiquidation.entireTroveDebt <= _THUSDInStabPool)) {
_movePendingTroveRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
assert(_THUSDInStabPool != 0);
_removeStake(_borrower);
singleLiquidation = _getCappedOffsetVals(singleLiquidation.entireTroveDebt, singleLiquidation.entireTroveColl, _price);
_closeTrove(_borrower, Status.closedByLiquidation);
if (singleLiquidation.collSurplus > 0) {
collSurplusPool.accountSurplus(_borrower, singleLiquidation.collSurplus);
}
emit TroveLiquidated(_borrower, singleLiquidation.entireTroveDebt, singleLiquidation.collToSendToSP, TroveManagerOperation.liquidateInRecoveryMode);
emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInRecoveryMode);
} else { // if (_ICR >= MCR && ( _ICR >= _TCR || singleLiquidation.entireTroveDebt > _THUSDInStabPool))
LiquidationValues memory zeroVals;
return zeroVals;
}
return singleLiquidation;
}
/* In a full liquidation, returns the values for a trove's coll and debt to be offset, and coll and debt to be
* redistributed to active troves.
*/
function _getOffsetAndRedistributionVals
(
uint256 _debt,
uint256 _coll,
uint256 _THUSDInStabPool
)
internal
pure
returns (uint256 debtToOffset, uint256 collToSendToSP, uint256 debtToRedistribute, uint256 collToRedistribute)
{
if (_THUSDInStabPool > 0) {
/*
* Offset as much debt & collateral as possible against the Stability Pool, and redistribute the remainder
* between all active troves.
*
* If the trove's debt is larger than the deposited THUSD in the Stability Pool:
*
* - Offset an amount of the trove's debt equal to the THUSD in the Stability Pool
* - Send a fraction of the trove's collateral to the Stability Pool, equal to the fraction of its offset debt
*
*/
debtToOffset = LiquityMath._min(_debt, _THUSDInStabPool);
collToSendToSP = _coll * debtToOffset / _debt;
debtToRedistribute = _debt - debtToOffset;
collToRedistribute = _coll - collToSendToSP;
} else {
debtToOffset = 0;
collToSendToSP = 0;
debtToRedistribute = _debt;
collToRedistribute = _coll;
}
}
/*
* Get its offset coll/debt and collateral gas comp, and close the trove.
*/
function _getCappedOffsetVals
(
uint256 _entireTroveDebt,
uint256 _entireTroveColl,
uint256 _price
)
internal
pure
returns (LiquidationValues memory singleLiquidation)
{
singleLiquidation.entireTroveDebt = _entireTroveDebt;
singleLiquidation.entireTroveColl = _entireTroveColl;
uint256 cappedCollPortion = _entireTroveDebt * MCR / _price;
singleLiquidation.collGasCompensation = _getCollGasCompensation(cappedCollPortion);
singleLiquidation.THUSDGasCompensation = THUSD_GAS_COMPENSATION;
singleLiquidation.debtToOffset = _entireTroveDebt;
singleLiquidation.collToSendToSP = cappedCollPortion - singleLiquidation.collGasCompensation;
singleLiquidation.collSurplus = _entireTroveColl - cappedCollPortion;
singleLiquidation.debtToRedistribute = 0;
singleLiquidation.collToRedistribute = 0;
}
/*
* Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves,
* starting from the one with the lowest collateral ratio in the system, and moving upwards
*/
function liquidateTroves(uint256 _n) external override {
ContractsCache memory contractsCache = ContractsCache(
activePool,
defaultPool,
ITHUSDToken(address(0)),
IPCV(address(0)),
sortedTroves,
ICollSurplusPool(address(0)),
address(0)
);
IStabilityPool stabilityPoolCached = stabilityPool;
LocalVariables_OuterLiquidationFunction memory vars;
LiquidationTotals memory totals;
vars.price = priceFeed.fetchPrice();
vars.THUSDInStabPool = stabilityPoolCached.getTotalTHUSDDeposits();
vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);
// Perform the appropriate liquidation sequence - tally the values, and obtain their totals
if (vars.recoveryModeAtStart) {
totals = _getTotalsFromLiquidateTrovesSequence_RecoveryMode(contractsCache, vars.price, vars.THUSDInStabPool, _n);
} else { // if !vars.recoveryModeAtStart
totals = _getTotalsFromLiquidateTrovesSequence_NormalMode(contractsCache.activePool, contractsCache.defaultPool, vars.price, vars.THUSDInStabPool, _n);
}
require(totals.totalDebtInSequence > 0, "TroveManager: nothing to liquidate");
// Move liquidated collateral and THUSD to the appropriate pools
stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP);
_redistributeDebtAndColl(contractsCache.activePool, contractsCache.defaultPool, totals.totalDebtToRedistribute, totals.totalCollToRedistribute);
if (totals.totalCollSurplus > 0) {
contractsCache.activePool.sendCollateral(address(collSurplusPool), totals.totalCollSurplus);
}
// Update system snapshots
_updateSystemSnapshots_excludeCollRemainder(contractsCache.activePool, totals.totalCollGasCompensation);
vars.liquidatedDebt = totals.totalDebtInSequence;
vars.liquidatedColl = totals.totalCollInSequence - totals.totalCollGasCompensation - totals.totalCollSurplus;
emit Liquidation(vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalTHUSDGasCompensation);
// Send gas compensation to caller
_sendGasCompensation(contractsCache.activePool, msg.sender, totals.totalTHUSDGasCompensation, totals.totalCollGasCompensation);
}
/*
* This function is used when the liquidateTroves sequence starts during Recovery Mode. However, it
* handle the case where the system *leaves* Recovery Mode, part way through the liquidation sequence
*/
function _getTotalsFromLiquidateTrovesSequence_RecoveryMode
(
ContractsCache memory _contractsCache,
uint256 _price,
uint256 _THUSDInStabPool,
uint256 _n
)
internal
returns(LiquidationTotals memory totals)
{
LocalVariables_LiquidationSequence memory vars;
LiquidationValues memory singleLiquidation;
vars.remainingTHUSDInStabPool = _THUSDInStabPool;
vars.backToNormalMode = false;
vars.entireSystemDebt = getEntireSystemDebt();
vars.entireSystemColl = getEntireSystemColl();
vars.user = _contractsCache.sortedTroves.getLast();
address firstUser = _contractsCache.sortedTroves.getFirst();
for (vars.i = 0; vars.i < _n && vars.user != firstUser; vars.i++) {
// we need to cache it, because current user is likely going to be deleted
address nextUser = _contractsCache.sortedTroves.getPrev(vars.user);
vars.ICR = getCurrentICR(vars.user, _price);
if (!vars.backToNormalMode) {
// Break the loop if ICR is greater than MCR and Stability Pool is empty
if (vars.ICR >= MCR && vars.remainingTHUSDInStabPool == 0) { break; }
uint256 TCR = LiquityMath._computeCR(vars.entireSystemColl, vars.entireSystemDebt, _price);
singleLiquidation = _liquidateRecoveryMode(_contractsCache.activePool, _contractsCache.defaultPool, vars.user, vars.ICR, vars.remainingTHUSDInStabPool, TCR, _price);
// Update aggregate trackers
vars.remainingTHUSDInStabPool -= singleLiquidation.debtToOffset;
vars.entireSystemDebt -= singleLiquidation.debtToOffset;
vars.entireSystemColl -= singleLiquidation.collToSendToSP
+ singleLiquidation.collGasCompensation
+ singleLiquidation.collSurplus;
// Add liquidation values to their respective running totals
totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
vars.backToNormalMode = !_checkPotentialRecoveryMode(vars.entireSystemColl, vars.entireSystemDebt, _price);
}
else if (vars.backToNormalMode && vars.ICR < MCR) {
singleLiquidation = _liquidateNormalMode(_contractsCache.activePool, _contractsCache.defaultPool, vars.user, vars.remainingTHUSDInStabPool);
vars.remainingTHUSDInStabPool -= singleLiquidation.debtToOffset;
// Add liquidation values to their respective running totals
totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
} else break; // break if the loop reaches a Trove with ICR >= MCR
vars.user = nextUser;
}
}
function _getTotalsFromLiquidateTrovesSequence_NormalMode
(
IActivePool _activePool,
IDefaultPool _defaultPool,
uint256 _price,
uint256 _THUSDInStabPool,
uint256 _n
)
internal
returns(LiquidationTotals memory totals)
{
LocalVariables_LiquidationSequence memory vars;
LiquidationValues memory singleLiquidation;
ISortedTroves sortedTrovesCached = sortedTroves;
vars.remainingTHUSDInStabPool = _THUSDInStabPool;
for (vars.i = 0; vars.i < _n; vars.i++) {
vars.user = sortedTrovesCached.getLast();
vars.ICR = getCurrentICR(vars.user, _price);
if (vars.ICR < MCR) {
singleLiquidation = _liquidateNormalMode(_activePool, _defaultPool, vars.user, vars.remainingTHUSDInStabPool);
vars.remainingTHUSDInStabPool -= singleLiquidation.debtToOffset;
// Add liquidation values to their respective running totals
totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
} else break; // break if the loop reaches a Trove with ICR >= MCR
}
}
/*
* Attempt to liquidate a custom list of troves provided by the caller.
*/
function batchLiquidateTroves(address[] memory _troveArray) public override {
require(_troveArray.length != 0, "TroveManager: Calldata address array must not be empty");
IActivePool activePoolCached = activePool;
IDefaultPool defaultPoolCached = defaultPool;
IStabilityPool stabilityPoolCached = stabilityPool;
LocalVariables_OuterLiquidationFunction memory vars;
LiquidationTotals memory totals;
vars.price = priceFeed.fetchPrice();
vars.THUSDInStabPool = stabilityPoolCached.getTotalTHUSDDeposits();
vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);
// Perform the appropriate liquidation sequence - tally values and obtain their totals.
if (vars.recoveryModeAtStart) {
totals = _getTotalFromBatchLiquidate_RecoveryMode(activePoolCached, defaultPoolCached, vars.price, vars.THUSDInStabPool, _troveArray);
} else { // if !vars.recoveryModeAtStart
totals = _getTotalsFromBatchLiquidate_NormalMode(activePoolCached, defaultPoolCached, vars.price, vars.THUSDInStabPool, _troveArray);
}
require(totals.totalDebtInSequence > 0, "TroveManager: nothing to liquidate");
// Move liquidated collateral and THUSD to the appropriate pools
stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP);
_redistributeDebtAndColl(activePoolCached, defaultPoolCached, totals.totalDebtToRedistribute, totals.totalCollToRedistribute);
if (totals.totalCollSurplus > 0) {
activePoolCached.sendCollateral(address(collSurplusPool), totals.totalCollSurplus);
}
// Update system snapshots
_updateSystemSnapshots_excludeCollRemainder(activePoolCached, totals.totalCollGasCompensation);
vars.liquidatedDebt = totals.totalDebtInSequence;
vars.liquidatedColl = totals.totalCollInSequence - totals.totalCollGasCompensation - totals.totalCollSurplus;
emit Liquidation(vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalTHUSDGasCompensation);
// Send gas compensation to caller
_sendGasCompensation(activePoolCached, msg.sender, totals.totalTHUSDGasCompensation, totals.totalCollGasCompensation);
}
/*
* This function is used when the batch liquidation sequence starts during Recovery Mode. However, it
* handle the case where the system *leaves* Recovery Mode, part way through the liquidation sequence
*/
function _getTotalFromBatchLiquidate_RecoveryMode
(
IActivePool _activePool,
IDefaultPool _defaultPool,
uint256 _price,
uint256 _THUSDInStabPool,
address[] memory _troveArray
)
internal
returns(LiquidationTotals memory totals)
{
LocalVariables_LiquidationSequence memory vars;
LiquidationValues memory singleLiquidation;
vars.remainingTHUSDInStabPool = _THUSDInStabPool;
vars.backToNormalMode = false;
vars.entireSystemDebt = getEntireSystemDebt();
vars.entireSystemColl = getEntireSystemColl();
for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {
vars.user = _troveArray[vars.i];
// Skip non-active troves
if (Troves[vars.user].status != Status.active) { continue; }
vars.ICR = getCurrentICR(vars.user, _price);
if (!vars.backToNormalMode) {
// Skip this trove if ICR is greater than MCR and Stability Pool is empty
if (vars.ICR >= MCR && vars.remainingTHUSDInStabPool == 0) { continue; }
uint256 TCR = LiquityMath._computeCR(vars.entireSystemColl, vars.entireSystemDebt, _price);
singleLiquidation = _liquidateRecoveryMode(_activePool, _defaultPool, vars.user, vars.ICR, vars.remainingTHUSDInStabPool, TCR, _price);
// Update aggregate trackers
vars.remainingTHUSDInStabPool -= singleLiquidation.debtToOffset;
vars.entireSystemDebt -= singleLiquidation.debtToOffset;
vars.entireSystemColl -= singleLiquidation.collToSendToSP
+ singleLiquidation.collGasCompensation
+ singleLiquidation.collSurplus;
// Add liquidation values to their respective running totals
totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
vars.backToNormalMode = !_checkPotentialRecoveryMode(vars.entireSystemColl, vars.entireSystemDebt, _price);
}
else if (vars.backToNormalMode && vars.ICR < MCR) {
singleLiquidation = _liquidateNormalMode(_activePool, _defaultPool, vars.user, vars.remainingTHUSDInStabPool);
vars.remainingTHUSDInStabPool -= singleLiquidation.debtToOffset;
// Add liquidation values to their respective running totals
totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
} else continue; // In Normal Mode skip troves with ICR >= MCR
}
}
function _getTotalsFromBatchLiquidate_NormalMode
(
IActivePool _activePool,
IDefaultPool _defaultPool,
uint256 _price,
uint256 _THUSDInStabPool,
address[] memory _troveArray
)
internal
returns(LiquidationTotals memory totals)
{
LocalVariables_LiquidationSequence memory vars;
LiquidationValues memory singleLiquidation;
vars.remainingTHUSDInStabPool = _THUSDInStabPool;
for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {
vars.user = _troveArray[vars.i];
vars.ICR = getCurrentICR(vars.user, _price);
if (vars.ICR < MCR) {
singleLiquidation = _liquidateNormalMode(_activePool, _defaultPool, vars.user, vars.remainingTHUSDInStabPool);
vars.remainingTHUSDInStabPool -= singleLiquidation.debtToOffset;
// Add liquidation values to their respective running totals
totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
}
}
}
// --- Liquidation helper functions ---
function _addLiquidationValuesToTotals(LiquidationTotals memory oldTotals, LiquidationValues memory singleLiquidation)
internal pure returns(LiquidationTotals memory newTotals) {
// Tally all the values with their respective running totals
newTotals.totalCollGasCompensation = oldTotals.totalCollGasCompensation + singleLiquidation.collGasCompensation;
newTotals.totalTHUSDGasCompensation = oldTotals.totalTHUSDGasCompensation + singleLiquidation.THUSDGasCompensation;
newTotals.totalDebtInSequence = oldTotals.totalDebtInSequence + singleLiquidation.entireTroveDebt;
newTotals.totalCollInSequence = oldTotals.totalCollInSequence + singleLiquidation.entireTroveColl;
newTotals.totalDebtToOffset = oldTotals.totalDebtToOffset + singleLiquidation.debtToOffset;
newTotals.totalCollToSendToSP = oldTotals.totalCollToSendToSP + singleLiquidation.collToSendToSP;
newTotals.totalDebtToRedistribute = oldTotals.totalDebtToRedistribute + singleLiquidation.debtToRedistribute;
newTotals.totalCollToRedistribute = oldTotals.totalCollToRedistribute + singleLiquidation.collToRedistribute;
newTotals.totalCollSurplus = oldTotals.totalCollSurplus + singleLiquidation.collSurplus;
return newTotals;
}
function _sendGasCompensation(IActivePool _activePool, address _liquidator, uint256 _THUSD, uint256 _collateral) internal {
if (_THUSD > 0) {
IGasPool(gasPoolAddress).sendTHUSD(_liquidator, _THUSD);
}
if (_collateral > 0) {
_activePool.sendCollateral(_liquidator, _collateral);
}
}
// Move a Trove's pending debt and collateral rewards from distributions, from the Default Pool to the Active Pool
function _movePendingTroveRewardsToActivePool(IActivePool _activePool, IDefaultPool _defaultPool, uint256 _THUSD, uint256 _collateral) internal {
_defaultPool.decreaseTHUSDDebt(_THUSD);
_activePool.increaseTHUSDDebt(_THUSD);
_defaultPool.sendCollateralToActivePool(_collateral);
}
// --- Redemption functions ---
// Redeem as much collateral as possible from _borrower's Trove in exchange for THUSD up to _maxTHUSDamount
function _redeemCollateralFromTrove(
ContractsCache memory _contractsCache,
address _borrower,
uint256 _maxTHUSDamount,
uint256 _price,
address _upperPartialRedemptionHint,
address _lowerPartialRedemptionHint,
uint256 _partialRedemptionHintNICR
)
internal returns (SingleRedemptionValues memory singleRedemption)
{
// Determine the remaining amount (lot) to be redeemed, capped by the entire debt of the Trove minus the liquidation reserve
singleRedemption.THUSDLot = LiquityMath._min(_maxTHUSDamount, Troves[_borrower].debt - THUSD_GAS_COMPENSATION);
// Get the collateralLot of equivalent value in USD
singleRedemption.collateralLot = singleRedemption.THUSDLot * DECIMAL_PRECISION / _price;
// Decrease the debt and collateral of the current Trove according to the THUSD lot and corresponding collateral to send
uint256 newDebt = Troves[_borrower].debt - singleRedemption.THUSDLot;
uint256 newColl = Troves[_borrower].coll - singleRedemption.collateralLot;
if (newDebt == THUSD_GAS_COMPENSATION) {
// No debt left in the Trove (except for the liquidation reserve), therefore the trove gets closed
_removeStake(_borrower);
_closeTrove(_borrower, Status.closedByRedemption);
_redeemCloseTrove(_contractsCache, _borrower, THUSD_GAS_COMPENSATION, newColl);
emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.redeemCollateral);
} else {
uint256 newNICR = LiquityMath._computeNominalCR(newColl, newDebt);
/*
* If the provided hint is out of date, we bail since trying to reinsert without a good hint will almost
* certainly result in running out of gas.
*
* If the resultant net debt of the partial is less than the minimum, net debt we bail.
*/
if (newNICR != _partialRedemptionHintNICR || _getNetDebt(newDebt) < MIN_NET_DEBT) {
singleRedemption.cancelledPartial = true;
return singleRedemption;
}
_contractsCache.sortedTroves.reInsert(_borrower, newNICR, _upperPartialRedemptionHint, _lowerPartialRedemptionHint);
Troves[_borrower].debt = newDebt;
Troves[_borrower].coll = newColl;
_updateStakeAndTotalStakes(_borrower);
emit TroveUpdated(
_borrower,
newDebt, newColl,
Troves[_borrower].stake,
TroveManagerOperation.redeemCollateral
);
}
return singleRedemption;
}
/*
* Called when a full redemption occurs, and closes the trove.
* The redeemer swaps (debt - liquidation reserve) THUSD for (debt - liquidation reserve) worth of collateral, so the THUSD liquidation reserve left corresponds to the remaining debt.
* In order to close the trove, the THUSD liquidation reserve is burned, and the corresponding debt is removed from the active pool.
* The debt recorded on the trove's struct is zero'd elswhere, in _closeTrove.
* Any surplus collateral left in the trove, is sent to the Coll surplus pool, and can be later claimed by the borrower.
*/
function _redeemCloseTrove(ContractsCache memory _contractsCache, address _borrower, uint256 _THUSD, uint256 _collateral) internal {
_contractsCache.thusdToken.burn(gasPoolAddress, _THUSD);
// Update Active Pool THUSD, and send collateral to account
_contractsCache.activePool.decreaseTHUSDDebt(_THUSD);
// send collateral from Active Pool to CollSurplus Pool
_contractsCache.collSurplusPool.accountSurplus(_borrower, _collateral);
_contractsCache.activePool.sendCollateral(address(_contractsCache.collSurplusPool), _collateral);
}
function _isValidFirstRedemptionHint(ISortedTroves _sortedTroves, address _firstRedemptionHint, uint256 _price) internal view returns (bool) {
if (_firstRedemptionHint == address(0) ||
!_sortedTroves.contains(_firstRedemptionHint) ||
getCurrentICR(_firstRedemptionHint, _price) < MCR
) {
return false;
}
address nextTrove = _sortedTroves.getNext(_firstRedemptionHint);
return nextTrove == address(0) || getCurrentICR(nextTrove, _price) < MCR;
}
/* Send _THUSDamount THUSD to the system and redeem the corresponding amount of collateral from as many Troves as are needed to fill the redemption
* request. Applies pending rewards to a Trove before reducing its debt and coll.
*
* Note that if _amount is very large, this function can run out of gas, specially if traversed troves are small. This can be easily avoided by
* splitting the total _amount in appropriate chunks and calling the function multiple times.
*
* Param `_maxIterations` can also be provided, so the loop through Troves is capped (if it’s zero, it will be ignored).This makes it easier to
* avoid OOG for the frontend, as only knowing approximately the average cost of an iteration is enough, without needing to know the “topology”
* of the trove list. It also avoids the need to set the cap in stone in the contract, nor doing gas calculations, as both gas price and opcode
* costs can vary.
*
* All Troves that are redeemed from -- with the likely exception of the last one -- will end up with no debt left, therefore they will be closed.
* If the last Trove does have some remaining debt, it has a finite ICR, and the reinsertion could be anywhere in the list, therefore it requires a hint.
* A frontend should use getRedemptionHints() to calculate what the ICR of this Trove will be after redemption, and pass a hint for its position
* in the sortedTroves list along with the ICR value that the hint was found for.
*
* If another transaction modifies the list between calling getRedemptionHints() and passing the hints to redeemCollateral(), it
* is very likely that the last (partially) redeemed Trove would end up with a different ICR than what the hint is for. In this case the
* redemption will stop after the last completely redeemed Trove and the sender will keep the remaining THUSD amount, which they can attempt
* to redeem later.
*/
function redeemCollateral(
uint256 _THUSDamount,
address _firstRedemptionHint,
address _upperPartialRedemptionHint,
address _lowerPartialRedemptionHint,
uint256 _partialRedemptionHintNICR,
uint256 _maxIterations,
uint256 _maxFeePercentage
)
external
override
{
ContractsCache memory contractsCache = ContractsCache(
activePool,
defaultPool,
thusdToken,
pcv,
sortedTroves,
collSurplusPool,
gasPoolAddress
);
RedemptionTotals memory totals;
_requireValidMaxFeePercentage(_maxFeePercentage);
totals.price = priceFeed.fetchPrice();
_requireTCRoverMCR(totals.price);
_requireAmountGreaterThanZero(_THUSDamount);
_requireTHUSDBalanceCoversRedemption(contractsCache.thusdToken, msg.sender, _THUSDamount);
totals.totalTHUSDDebtAtStart = getEntireSystemDebt();
totals.remainingTHUSD = _THUSDamount;
address currentBorrower;
if (_isValidFirstRedemptionHint(contractsCache.sortedTroves, _firstRedemptionHint, totals.price)) {
currentBorrower = _firstRedemptionHint;
} else {
currentBorrower = contractsCache.sortedTroves.getLast();
// Find the first trove with ICR >= MCR
while (currentBorrower != address(0) && getCurrentICR(currentBorrower, totals.price) < MCR) {
currentBorrower = contractsCache.sortedTroves.getPrev(currentBorrower);
}
}
// Loop through the Troves starting from the one with lowest collateral ratio until _amount of THUSD is exchanged for collateral
if (_maxIterations == 0) { _maxIterations = type(uint256).max; }
while (currentBorrower != address(0) && totals.remainingTHUSD > 0 && _maxIterations > 0) {
_maxIterations--;
// Save the address of the Trove preceding the current one, before potentially modifying the list
address nextUserToCheck = contractsCache.sortedTroves.getPrev(currentBorrower);
_applyPendingRewards(contractsCache.activePool, contractsCache.defaultPool, currentBorrower);
SingleRedemptionValues memory singleRedemption = _redeemCollateralFromTrove(
contractsCache,
currentBorrower,
totals.remainingTHUSD,
totals.price,
_upperPartialRedemptionHint,
_lowerPartialRedemptionHint,
_partialRedemptionHintNICR
);
if (singleRedemption.cancelledPartial) break; // Partial redemption was cancelled (out-of-date hint, or new net debt < minimum), therefore we could not redeem from the last Trove
totals.totalTHUSDToRedeem += singleRedemption.THUSDLot;
totals.totalCollateralDrawn += singleRedemption.collateralLot;
totals.remainingTHUSD -= singleRedemption.THUSDLot;
currentBorrower = nextUserToCheck;
}
require(totals.totalCollateralDrawn > 0, "TroveManager: Unable to redeem any amount");
// Decay the baseRate due to time passed, and then increase it according to the size of this redemption.
// Use the saved total THUSD supply value, from before it was reduced by the redemption.
_updateBaseRateFromRedemption(totals.totalCollateralDrawn, totals.price, totals.totalTHUSDDebtAtStart);
// Calculate the collateral fee
totals.collateralFee = _getRedemptionFee(totals.totalCollateralDrawn);
_requireUserAcceptsFee(totals.collateralFee, totals.totalCollateralDrawn, _maxFeePercentage);
// Send the collateral fee to the PCV contract
contractsCache.activePool.sendCollateral(address(contractsCache.pcv), totals.collateralFee);
totals.collateralToSendToRedeemer = totals.totalCollateralDrawn - totals.collateralFee;
emit Redemption(_THUSDamount, totals.totalTHUSDToRedeem, totals.totalCollateralDrawn, totals.collateralFee);
// Burn the total THUSD that is cancelled with debt, and send the redeemed collateral to msg.sender
contractsCache.thusdToken.burn(msg.sender, totals.totalTHUSDToRedeem);
// Update Active Pool THUSD, and send collateral to account
contractsCache.activePool.decreaseTHUSDDebt(totals.totalTHUSDToRedeem);
contractsCache.activePool.sendCollateral(msg.sender, totals.collateralToSendToRedeemer);
}
// --- Helper functions ---
// Return the nominal collateral ratio (ICR) of a given Trove, without the price. Takes a trove's pending coll and debt rewards from redistributions into account.
function getNominalICR(address _borrower) public view override returns (uint) {
(uint256 currentCollateral, uint256 currentTHUSDDebt) = _getCurrentTroveAmounts(_borrower);
uint256 NICR = LiquityMath._computeNominalCR(currentCollateral, currentTHUSDDebt);
return NICR;
}
// Return the current collateral ratio (ICR) of a given Trove. Takes a trove's pending coll and debt rewards from redistributions into account.
function getCurrentICR(address _borrower, uint256 _price) public view override returns (uint) {
(uint256 currentCollateral, uint256 currentTHUSDDebt) = _getCurrentTroveAmounts(_borrower);
uint256 ICR = LiquityMath._computeCR(currentCollateral, currentTHUSDDebt, _price);
return ICR;
}
function _getCurrentTroveAmounts(address _borrower) internal view returns (uint, uint) {
uint256 pendingCollateralReward = getPendingCollateralReward(_borrower);
uint256 pendingTHUSDDebtReward = getPendingTHUSDDebtReward(_borrower);
uint256 currentCollateral = Troves[_borrower].coll + pendingCollateralReward;
uint256 currentTHUSDDebt = Troves[_borrower].debt + pendingTHUSDDebtReward;
return (currentCollateral, currentTHUSDDebt);
}
function applyPendingRewards(address _borrower) external override {
_requireCallerIsBorrowerOperations();
return _applyPendingRewards(activePool, defaultPool, _borrower);
}
// Add the borrowers's coll and debt rewards earned from redistributions, to their Trove
function _applyPendingRewards(IActivePool _activePool, IDefaultPool _defaultPool, address _borrower) internal {
if (hasPendingRewards(_borrower)) {
_requireTroveIsActive(_borrower);
// Compute pending rewards
uint256 pendingCollateralReward = getPendingCollateralReward(_borrower);
uint256 pendingTHUSDDebtReward = getPendingTHUSDDebtReward(_borrower);
// Apply pending rewards to trove's state
Troves[_borrower].coll += pendingCollateralReward;
Troves[_borrower].debt += pendingTHUSDDebtReward;
_updateTroveRewardSnapshots(_borrower);
// Transfer from DefaultPool to ActivePool
_movePendingTroveRewardsToActivePool(_activePool, _defaultPool, pendingTHUSDDebtReward, pendingCollateralReward);
emit TroveUpdated(
_borrower,
Troves[_borrower].debt,
Troves[_borrower].coll,
Troves[_borrower].stake,
TroveManagerOperation.applyPendingRewards
);
}
}
// Update borrower's snapshots of L_Collateral and L_THUSDDebt to reflect the current values
function updateTroveRewardSnapshots(address _borrower) external override {
_requireCallerIsBorrowerOperations();
return _updateTroveRewardSnapshots(_borrower);
}
function _updateTroveRewardSnapshots(address _borrower) internal {
rewardSnapshots[_borrower].collateral = L_Collateral;
rewardSnapshots[_borrower].THUSDDebt = L_THUSDDebt;
emit TroveSnapshotsUpdated(L_Collateral, L_THUSDDebt);
}
// Get the borrower's pending accumulated collateral reward, earned by their stake
function getPendingCollateralReward(address _borrower) public view override returns (uint) {
uint256 snapshotCollateral = rewardSnapshots[_borrower].collateral;
uint256 rewardPerUnitStaked = L_Collateral - snapshotCollateral;
if ( rewardPerUnitStaked == 0 || Troves[_borrower].status != Status.active) { return 0; }
uint256 stake = Troves[_borrower].stake;
uint256 pendingCollateralReward = stake * rewardPerUnitStaked / DECIMAL_PRECISION;
return pendingCollateralReward;
}
// Get the borrower's pending accumulated THUSD reward, earned by their stake
function getPendingTHUSDDebtReward(address _borrower) public view override returns (uint) {
uint256 snapshotTHUSDDebt = rewardSnapshots[_borrower].THUSDDebt;
uint256 rewardPerUnitStaked = L_THUSDDebt - snapshotTHUSDDebt;
if ( rewardPerUnitStaked == 0 || Troves[_borrower].status != Status.active) { return 0; }
uint256 stake = Troves[_borrower].stake;
uint256 pendingTHUSDDebtReward = stake * rewardPerUnitStaked / DECIMAL_PRECISION;
return pendingTHUSDDebtReward;
}
function hasPendingRewards(address _borrower) public view override returns (bool) {
/*
* A Trove has pending rewards if its snapshot is less than the current rewards per-unit-staked sum:
* this indicates that rewards have occured since the snapshot was made, and the user therefore has
* pending rewards
*/
if (Troves[_borrower].status != Status.active) {return false;}
return (rewardSnapshots[_borrower].collateral < L_Collateral);
}
// Return the Troves entire debt and coll, including pending rewards from redistributions.
function getEntireDebtAndColl(
address _borrower
)
public
view
override
returns (uint256 debt, uint256 coll, uint256 pendingTHUSDDebtReward, uint256 pendingCollateralReward)
{
debt = Troves[_borrower].debt;
coll = Troves[_borrower].coll;
pendingTHUSDDebtReward = getPendingTHUSDDebtReward(_borrower);
pendingCollateralReward = getPendingCollateralReward(_borrower);
debt += pendingTHUSDDebtReward;
coll += pendingCollateralReward;
}
function removeStake(address _borrower) external override {
_requireCallerIsBorrowerOperations();
return _removeStake(_borrower);
}
// Remove borrower's stake from the totalStakes sum, and set their stake to 0
function _removeStake(address _borrower) internal {
uint256 stake = Troves[_borrower].stake;
totalStakes -= stake;
Troves[_borrower].stake = 0;
}
function updateStakeAndTotalStakes(address _borrower) external override returns (uint) {
_requireCallerIsBorrowerOperations();
return _updateStakeAndTotalStakes(_borrower);
}
// Update borrower's stake based on their latest collateral value
function _updateStakeAndTotalStakes(address _borrower) internal returns (uint) {
uint256 newStake = _computeNewStake(Troves[_borrower].coll);
uint256 oldStake = Troves[_borrower].stake;
Troves[_borrower].stake = newStake;
totalStakes = totalStakes - oldStake + newStake;
emit TotalStakesUpdated(totalStakes);
return newStake;
}
// Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at the last liquidation
function _computeNewStake(uint256 _coll) internal view returns (uint) {
uint256 stake;
if (totalCollateralSnapshot == 0) {
stake = _coll;
} else {
/*
* The following assert() holds true because:
* - The system always contains >= 1 trove
* - When we close or liquidate a trove, we redistribute the pending rewards, so if all troves were closed/liquidated,
* rewards would’ve been emptied and totalCollateralSnapshot would be zero too.
*/
assert(totalStakesSnapshot > 0);
stake = _coll * totalStakesSnapshot / totalCollateralSnapshot;
}
return stake;
}
function _redistributeDebtAndColl(IActivePool _activePool, IDefaultPool _defaultPool, uint256 _debt, uint256 _coll) internal {
if (_debt == 0) { return; }
/*
* Add distributed coll and debt rewards-per-unit-staked to the running totals. Division uses a "feedback"
* error correction, to keep the cumulative error low in the running totals L_Collateral and L_THUSDDebt:
*
* 1) Form numerators which compensate for the floor division errors that occurred the last time this
* function was called.
* 2) Calculate "per-unit-staked" ratios.
* 3) Multiply each ratio back by its denominator, to reveal the current floor division error.
* 4) Store these errors for use in the next correction when this function is called.
* 5) Note: static analysis tools complain about this "division before multiplication", however, it is intended.
*/
uint256 collateralNumerator = _coll * DECIMAL_PRECISION + lastCollateralError_Redistribution;
uint256 THUSDDebtNumerator = _debt * DECIMAL_PRECISION + lastTHUSDDebtError_Redistribution;
// Get the per-unit-staked terms
uint256 collateralRewardPerUnitStaked = collateralNumerator / totalStakes;
uint256 THUSDDebtRewardPerUnitStaked = THUSDDebtNumerator / totalStakes;
lastCollateralError_Redistribution = collateralNumerator - (collateralRewardPerUnitStaked * totalStakes);
lastTHUSDDebtError_Redistribution = THUSDDebtNumerator - (THUSDDebtRewardPerUnitStaked * totalStakes);
// Add per-unit-staked terms to the running totals
L_Collateral += collateralRewardPerUnitStaked;
L_THUSDDebt += THUSDDebtRewardPerUnitStaked;
emit LTermsUpdated(L_Collateral, L_THUSDDebt);
// Transfer coll and debt from ActivePool to DefaultPool
_activePool.decreaseTHUSDDebt(_debt);
_defaultPool.increaseTHUSDDebt(_debt);
_activePool.sendCollateral(address(_defaultPool), _coll);
}
function closeTrove(address _borrower) external override {
_requireCallerIsBorrowerOperations();
return _closeTrove(_borrower, Status.closedByOwner);
}
function _closeTrove(address _borrower, Status closedStatus) internal {
assert(closedStatus != Status.nonExistent && closedStatus != Status.active);
uint256 TroveOwnersArrayLength = TroveOwners.length;
if (thusdToken.mintList(borrowerOperationsAddress)) {
_requireMoreThanOneTroveInSystem(TroveOwnersArrayLength);
}
Troves[_borrower].status = closedStatus;
Troves[_borrower].coll = 0;
Troves[_borrower].debt = 0;
rewardSnapshots[_borrower].collateral = 0;
rewardSnapshots[_borrower].THUSDDebt = 0;
_removeTroveOwner(_borrower, TroveOwnersArrayLength);
sortedTroves.remove(_borrower);
}
/*
* Updates snapshots of system total stakes and total collateral, excluding a given collateral remainder from the calculation.
* Used in a liquidation sequence.
*
* The calculation excludes a portion of collateral that is in the ActivePool:
*
* the total collateral gas compensation from the liquidation sequence
*
* The collateral as compensation must be excluded as it is always sent out at the very end of the liquidation sequence.
*/
function _updateSystemSnapshots_excludeCollRemainder(IActivePool _activePool, uint256 _collRemainder) internal {
totalStakesSnapshot = totalStakes;
uint256 activeColl = _activePool.getCollateralBalance();
uint256 liquidatedColl = defaultPool.getCollateralBalance();
totalCollateralSnapshot = activeColl - _collRemainder + liquidatedColl;
emit SystemSnapshotsUpdated(totalStakesSnapshot, totalCollateralSnapshot);
}
// Push the owner's address to the Trove owners list, and record the corresponding array index on the Trove struct
function addTroveOwnerToArray(address _borrower) external override returns (uint256 index) {
_requireCallerIsBorrowerOperations();
return _addTroveOwnerToArray(_borrower);
}
function _addTroveOwnerToArray(address _borrower) internal returns (uint128 index) {
/* Max array size is 2**128 - 1, i.e. ~3e30 troves. No risk of overflow, since troves have minimum THUSD
debt of liquidation reserve plus MIN_NET_DEBT. 3e30 THUSD dwarfs the value of all wealth in the world ( which is < 1e15 USD). */
// Push the Troveowner to the array
TroveOwners.push(_borrower);
// Record the index of the new Troveowner on their Trove struct
index = uint128(TroveOwners.length - 1);
Troves[_borrower].arrayIndex = index;
return index;
}
/*
* Remove a Trove owner from the TroveOwners array, not preserving array order. Removing owner 'B' does the following:
* [A B C D E] => [A E C D], and updates E's Trove struct to point to its new array index.
*/
function _removeTroveOwner(address _borrower, uint256 TroveOwnersArrayLength) internal {
Status troveStatus = Troves[_borrower].status;
// It’s set in caller function `_closeTrove`
assert(troveStatus != Status.nonExistent && troveStatus != Status.active);
uint128 index = Troves[_borrower].arrayIndex;
uint256 length = TroveOwnersArrayLength;
uint256 idxLast = length - 1;
assert(index <= idxLast);
address addressToMove = TroveOwners[idxLast];
TroveOwners[index] = addressToMove;
Troves[addressToMove].arrayIndex = index;
emit TroveIndexUpdated(addressToMove, index);
TroveOwners.pop();
}
// --- Recovery Mode and TCR functions ---
function getTCR(uint256 _price) external view override returns (uint) {
return _getTCR(_price);
}
function checkRecoveryMode(uint256 _price) external view override returns (bool) {
return _checkRecoveryMode(_price);
}
// Check whether or not the system *would be* in Recovery Mode, given an collateral:USD price, and the entire system coll and debt.
function _checkPotentialRecoveryMode(
uint256 _entireSystemColl,
uint256 _entireSystemDebt,
uint256 _price
)
internal
pure
returns (bool)
{
uint256 TCR = LiquityMath._computeCR(_entireSystemColl, _entireSystemDebt, _price);
return TCR < CCR;
}
// --- Redemption fee functions ---
/*
* This function has two impacts on the baseRate state variable:
* 1) decays the baseRate based on time passed since last redemption or THUSD borrowing operation.
* then,
* 2) increases the baseRate based on the amount redeemed, as a proportion of total debt
*/
function _updateBaseRateFromRedemption(uint256 _collateralDrawn, uint256 _price, uint256 _totalTHUSDDebt) internal returns (uint) {
uint256 decayedBaseRate = _calcDecayedBaseRate();
/* Convert the drawn collateral back to THUSD at face value rate (1 THUSD:1 USD), in order to get
* the fraction of total supply that was redeemed at face value. */
uint256 redeemedTHUSDFraction = _collateralDrawn * _price / _totalTHUSDDebt;
uint256 newBaseRate = decayedBaseRate + (redeemedTHUSDFraction / BETA);
newBaseRate = LiquityMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100%
//assert(newBaseRate <= DECIMAL_PRECISION); // This is already enforced in the line above
assert(newBaseRate > 0); // Base rate is always non-zero after redemption
// Update the baseRate state variable
baseRate = newBaseRate;
emit BaseRateUpdated(newBaseRate);
_updateLastFeeOpTime();
return newBaseRate;
}
function getRedemptionRate() public view override returns (uint) {
return _calcRedemptionRate(baseRate);
}
function getRedemptionRateWithDecay() public view override returns (uint) {
return _calcRedemptionRate(_calcDecayedBaseRate());
}
function _calcRedemptionRate(uint256 _baseRate) internal pure returns (uint) {
return LiquityMath._min(
REDEMPTION_FEE_FLOOR + _baseRate,
DECIMAL_PRECISION // cap at a maximum of 100%
);
}
function _getRedemptionFee(uint256 _collateralDrawn) internal view returns (uint) {
return _calcRedemptionFee(getRedemptionRate(), _collateralDrawn);
}
function getRedemptionFeeWithDecay(uint256 _collateralDrawn) external view override returns (uint) {
return _calcRedemptionFee(getRedemptionRateWithDecay(), _collateralDrawn);
}
function _calcRedemptionFee(uint256 _redemptionRate, uint256 _collateralDrawn) internal pure returns (uint) {
uint256 redemptionFee = _redemptionRate * _collateralDrawn / DECIMAL_PRECISION;
require(redemptionFee < _collateralDrawn, "TroveManager: Fee would eat up all returned collateral");
return redemptionFee;
}
// --- Borrowing fee functions ---
function getBorrowingRate() public view override returns (uint) {
return _calcBorrowingRate(baseRate);
}
function getBorrowingRateWithDecay() public view override returns (uint) {
return _calcBorrowingRate(_calcDecayedBaseRate());
}
function _calcBorrowingRate(uint256 _baseRate) internal pure returns (uint) {
return LiquityMath._min(
BORROWING_FEE_FLOOR + _baseRate,
MAX_BORROWING_FEE
);
}
function getBorrowingFee(uint256 _THUSDDebt) external view override returns (uint) {
return _calcBorrowingFee(getBorrowingRate(), _THUSDDebt);
}
function getBorrowingFeeWithDecay(uint256 _THUSDDebt) external view override returns (uint) {
return _calcBorrowingFee(getBorrowingRateWithDecay(), _THUSDDebt);
}
function _calcBorrowingFee(uint256 _borrowingRate, uint256 _THUSDDebt) internal pure returns (uint) {
return _borrowingRate * _THUSDDebt / DECIMAL_PRECISION;
}
// Updates the baseRate state variable based on time elapsed since the last redemption or THUSD borrowing operation.
function decayBaseRateFromBorrowing() external override {
_requireCallerIsBorrowerOperations();
uint256 decayedBaseRate = _calcDecayedBaseRate();
assert(decayedBaseRate <= DECIMAL_PRECISION); // The baseRate can decay to 0
baseRate = decayedBaseRate;
emit BaseRateUpdated(decayedBaseRate);
_updateLastFeeOpTime();
}
// --- Internal fee functions ---
// Update the last fee operation time only if time passed >= decay interval. This prevents base rate griefing.
function _updateLastFeeOpTime() internal {
uint256 timePassed = block.timestamp - lastFeeOperationTime;
if (timePassed >= 1 minutes) {
lastFeeOperationTime = block.timestamp;
emit LastFeeOpTimeUpdated(block.timestamp);
}
}
function _calcDecayedBaseRate() internal view returns (uint) {
uint256 minutesPassed = _minutesPassedSinceLastFeeOp();
uint256 decayFactor = LiquityMath._decPow(MINUTE_DECAY_FACTOR, minutesPassed);
return baseRate * decayFactor / DECIMAL_PRECISION;
}
function _minutesPassedSinceLastFeeOp() internal view returns (uint) {
return (block.timestamp - lastFeeOperationTime) / 1 minutes;
}
// --- 'require' wrapper functions ---
function _requireCallerIsBorrowerOperations() internal view {
require(msg.sender == borrowerOperationsAddress, "TroveManager: Caller is not the BorrowerOperations contract");
}
function _requireTroveIsActive(address _borrower) internal view {
require(Troves[_borrower].status == Status.active, "TroveManager: Trove does not exist or is closed");
}
function _requireTHUSDBalanceCoversRedemption(ITHUSDToken _thusdToken, address _redeemer, uint256 _amount) internal view {
require(_thusdToken.balanceOf(_redeemer) >= _amount, "TroveManager: Requested redemption amount must be <= user's THUSD token balance");
}
function _requireMoreThanOneTroveInSystem(uint256 TroveOwnersArrayLength) internal view {
require (TroveOwnersArrayLength > 1 && sortedTroves.getSize() > 1, "TroveManager: Only one trove in the system");
}
function _requireAmountGreaterThanZero(uint256 _amount) internal pure {
require(_amount > 0, "TroveManager: Amount must be greater than zero");
}
function _requireTCRoverMCR(uint256 _price) internal view {
require(_getTCR(_price) >= MCR, "TroveManager: Cannot redeem when TCR < MCR");
}
function _requireValidMaxFeePercentage(uint256 _maxFeePercentage) internal pure {
require(_maxFeePercentage >= REDEMPTION_FEE_FLOOR && _maxFeePercentage <= DECIMAL_PRECISION,
"Max fee percentage must be between 0.5% and 100%");
}
// --- Trove property getters ---
function getTroveStatus(address _borrower) external view override returns (Status) {
return Troves[_borrower].status;
}
function getTroveStake(address _borrower) external view override returns (uint) {
return Troves[_borrower].stake;
}
function getTroveDebt(address _borrower) external view override returns (uint) {
return Troves[_borrower].debt;
}
function getTroveColl(address _borrower) external view override returns (uint) {
return Troves[_borrower].coll;
}
// --- Trove property setters, called by BorrowerOperations ---
function setTroveStatus(address _borrower, Status _status) external override {
_requireCallerIsBorrowerOperations();
Troves[_borrower].status = _status;
}
function increaseTroveColl(address _borrower, uint256 _collIncrease) external override returns (uint) {
_requireCallerIsBorrowerOperations();
uint256 newColl = Troves[_borrower].coll + _collIncrease;
Troves[_borrower].coll = newColl;
return newColl;
}
function decreaseTroveColl(address _borrower, uint256 _collDecrease) external override returns (uint) {
_requireCallerIsBorrowerOperations();
uint256 newColl = Troves[_borrower].coll - _collDecrease;
Troves[_borrower].coll = newColl;
return newColl;
}
function increaseTroveDebt(address _borrower, uint256 _debtIncrease) external override returns (uint) {
_requireCallerIsBorrowerOperations();
uint256 newDebt = Troves[_borrower].debt + _debtIncrease;
Troves[_borrower].debt = newDebt;
return newDebt;
}
function decreaseTroveDebt(address _borrower, uint256 _debtDecrease) external override returns (uint) {
_requireCallerIsBorrowerOperations();
uint256 newDebt = Troves[_borrower].debt - _debtDecrease;
Troves[_borrower].debt = newDebt;
return newDebt;
}
}
{
"compilationTarget": {
"contracts/TroveManager.sol": "TroveManager"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 100
},
"remappings": []
}
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_activePoolAddress","type":"address"}],"name":"ActivePoolAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_baseRate","type":"uint256"}],"name":"BaseRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_newBorrowerOperationsAddress","type":"address"}],"name":"BorrowerOperationsAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_collSurplusPoolAddress","type":"address"}],"name":"CollSurplusPoolAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_defaultPoolAddress","type":"address"}],"name":"DefaultPoolAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_gasPoolAddress","type":"address"}],"name":"GasPoolAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_L_Collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_L_THUSDDebt","type":"uint256"}],"name":"LTermsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_lastFeeOpTime","type":"uint256"}],"name":"LastFeeOpTimeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_liquidatedDebt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_liquidatedColl","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_collGasCompensation","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_THUSDGasCompensation","type":"uint256"}],"name":"Liquidation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_pcvAddress","type":"address"}],"name":"PCVAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_newPriceFeedAddress","type":"address"}],"name":"PriceFeedAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_attemptedTHUSDAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_actualTHUSDAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_collateralSent","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_collateralFee","type":"uint256"}],"name":"Redemption","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_sortedTrovesAddress","type":"address"}],"name":"SortedTrovesAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_stabilityPoolAddress","type":"address"}],"name":"StabilityPoolAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_totalStakesSnapshot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_totalCollateralSnapshot","type":"uint256"}],"name":"SystemSnapshotsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_newTHUSDTokenAddress","type":"address"}],"name":"THUSDTokenAddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_newTotalStakes","type":"uint256"}],"name":"TotalStakesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_borrower","type":"address"},{"indexed":false,"internalType":"uint256","name":"_newIndex","type":"uint256"}],"name":"TroveIndexUpdated","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":"_coll","type":"uint256"},{"indexed":false,"internalType":"enum TroveManager.TroveManagerOperation","name":"_operation","type":"uint8"}],"name":"TroveLiquidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_L_Collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_L_THUSDDebt","type":"uint256"}],"name":"TroveSnapshotsUpdated","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":"_coll","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_stake","type":"uint256"},{"indexed":false,"internalType":"enum TroveManager.TroveManagerOperation","name":"_operation","type":"uint8"}],"name":"TroveUpdated","type":"event"},{"inputs":[],"name":"BETA","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BORROWING_FEE_FLOOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CCR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DECIMAL_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"L_Collateral","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"L_THUSDDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BORROWING_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MCR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINUTE_DECAY_FACTOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_NET_DEBT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERCENT_DIVISOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REDEMPTION_FEE_FLOOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"THUSD_GAS_COMPENSATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"TroveOwners","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"Troves","outputs":[{"internalType":"uint256","name":"debt","type":"uint256"},{"internalType":"uint256","name":"coll","type":"uint256"},{"internalType":"uint256","name":"stake","type":"uint256"},{"internalType":"enum ITroveManager.Status","name":"status","type":"uint8"},{"internalType":"uint128","name":"arrayIndex","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_100pct","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"activePool","outputs":[{"internalType":"contract IActivePool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"addTroveOwnerToArray","outputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"applyPendingRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"baseRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_troveArray","type":"address[]"}],"name":"batchLiquidateTroves","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"borrowerOperationsAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"checkRecoveryMode","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"closeTrove","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decayBaseRateFromBorrowing","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"},{"internalType":"uint256","name":"_collDecrease","type":"uint256"}],"name":"decreaseTroveColl","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"},{"internalType":"uint256","name":"_debtDecrease","type":"uint256"}],"name":"decreaseTroveDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"defaultPool","outputs":[{"internalType":"contract IDefaultPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_THUSDDebt","type":"uint256"}],"name":"getBorrowingFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_THUSDDebt","type":"uint256"}],"name":"getBorrowingFeeWithDecay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBorrowingRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBorrowingRateWithDecay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"},{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"getCurrentICR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getEntireDebtAndColl","outputs":[{"internalType":"uint256","name":"debt","type":"uint256"},{"internalType":"uint256","name":"coll","type":"uint256"},{"internalType":"uint256","name":"pendingTHUSDDebtReward","type":"uint256"},{"internalType":"uint256","name":"pendingCollateralReward","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getEntireSystemColl","outputs":[{"internalType":"uint256","name":"entireSystemColl","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getEntireSystemDebt","outputs":[{"internalType":"uint256","name":"entireSystemDebt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getNominalICR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getPendingCollateralReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getPendingTHUSDDebtReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_collateralDrawn","type":"uint256"}],"name":"getRedemptionFeeWithDecay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRedemptionRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRedemptionRateWithDecay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_price","type":"uint256"}],"name":"getTCR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getTroveColl","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getTroveDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getTroveFromTroveOwnersArray","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTroveOwnersCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getTroveStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"getTroveStatus","outputs":[{"internalType":"enum ITroveManager.Status","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"hasPendingRewards","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"},{"internalType":"uint256","name":"_collIncrease","type":"uint256"}],"name":"increaseTroveColl","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"},{"internalType":"uint256","name":"_debtIncrease","type":"uint256"}],"name":"increaseTroveDebt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastCollateralError_Redistribution","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastFeeOperationTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastTHUSDDebtError_Redistribution","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"liquidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_n","type":"uint256"}],"name":"liquidateTroves","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pcv","outputs":[{"internalType":"contract IPCV","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceFeed","outputs":[{"internalType":"contract IPriceFeed","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_THUSDamount","type":"uint256"},{"internalType":"address","name":"_firstRedemptionHint","type":"address"},{"internalType":"address","name":"_upperPartialRedemptionHint","type":"address"},{"internalType":"address","name":"_lowerPartialRedemptionHint","type":"address"},{"internalType":"uint256","name":"_partialRedemptionHintNICR","type":"uint256"},{"internalType":"uint256","name":"_maxIterations","type":"uint256"},{"internalType":"uint256","name":"_maxFeePercentage","type":"uint256"}],"name":"redeemCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"removeStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardSnapshots","outputs":[{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"THUSDDebt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_borrowerOperationsAddress","type":"address"},{"internalType":"address","name":"_activePoolAddress","type":"address"},{"internalType":"address","name":"_defaultPoolAddress","type":"address"},{"internalType":"address","name":"_stabilityPoolAddress","type":"address"},{"internalType":"address","name":"_gasPoolAddress","type":"address"},{"internalType":"address","name":"_collSurplusPoolAddress","type":"address"},{"internalType":"address","name":"_priceFeedAddress","type":"address"},{"internalType":"address","name":"_thusdTokenAddress","type":"address"},{"internalType":"address","name":"_sortedTrovesAddress","type":"address"},{"internalType":"address","name":"_pcvAddress","type":"address"}],"name":"setAddresses","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"},{"internalType":"enum ITroveManager.Status","name":"_status","type":"uint8"}],"name":"setTroveStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sortedTroves","outputs":[{"internalType":"contract ISortedTroves","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stabilityPool","outputs":[{"internalType":"contract IStabilityPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"thusdToken","outputs":[{"internalType":"contract ITHUSDToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalCollateralSnapshot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStakes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStakesSnapshot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"updateStakeAndTotalStakes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_borrower","type":"address"}],"name":"updateTroveRewardSnapshots","outputs":[],"stateMutability":"nonpayable","type":"function"}]