Liquity Protocol. What is a Stability Pool? How do liquidations and redemptions work?
In a previous article, I started explaining Liquity protocol from its smart contracts “Liquity Protocol — DeFi protocol explained”, where I went through its native tokens and its BorrowerOperations.sol
among other things.
And in this article, we will continue going through three of the most important and interesting parts of the protocol:
Stability Pool
Liquidations
Redemptions
Stability Pool
The Stability Pool, as the name suggests, ensures that the protocol remains stable and can operate at peak efficiency.
When any Trove is liquidated, an amount of LUSD corresponding to the remaining debt of the Trove is burned from the Stability Pool’s balance to repay its debt. In exchange, the entire collateral from the Trove is transferred to the Stability Pool.
The necessary amount of LUSD is removed from the pool to cancel out the debt, and then the locked Ethereum is distributed proportionally to the pool’s stakers. These are known as “liquidation gains.”
The smart contracts involved:
StabilityPool.sol
Contains functionality for Stability Pool operations: making deposits, and withdrawing compounded deposits and accumulated ETH and LQTY gains.
Holds the LUSD Stability Pool deposits and the ETH gains for depositors, from liquidations.
Some of StabilityPool’s main functions are:
provideToSP(uint _amount, address _frontEndTag)
: allows stablecoin holders to deposit_amount
of LUSD to the Stability Pool.withdrawFromSP(uint _amount)
: allows a stablecoin holder to withdraw_amount
of LUSD from the Stability Pool, up to the value of their remaining Stability deposit.withdrawETHGainToTrove(address _hint)
: sends the user's entire accumulated ETH gain to the user's active Trove, and updates their Stability deposit with its accumulated loss from debt absorptions.registerFrontEnd(uint _kickbackRate)
: Registers an address as a front end and sets their chosen kickback rate in the range[0,1]
.
Along with StabilityPool.sol
, these contracts hold Ether and/or tokens for their respective parts of the system, and contain minimal logic:
ActivePool.sol
— holds the total Ether balance and records the total stablecoin debt of the active Troves.
function sendETH(address _account, uint _amount) external override {
_requireCallerIsBOorTroveMorSP();
ETH = ETH.sub(_amount);
emit ActivePoolETHBalanceUpdated(ETH);
emit EtherSent(_account, _amount);
(bool success, ) = _account.call{ value: _amount }("");
require(success, "ActivePool: sending ETH failed");
}
function increaseLUSDDebt(uint _amount) external override {
_requireCallerIsBOorTroveM();
LUSDDebt = LUSDDebt.add(_amount);
ActivePoolLUSDDebtUpdated(LUSDDebt);
}
function decreaseLUSDDebt(uint _amount) external override {
_requireCallerIsBOorTroveMorSP();
LUSDDebt = LUSDDebt.sub(_amount);
ActivePoolLUSDDebtUpdated(LUSDDebt);
}
DefaultPool.sol
— :
holds the total Ether balance and records the total stablecoin debt of the liquidated Troves that are pending redistribution to active Troves.
If a Trove has pending ether/debt “rewards” in the DefaultPool, then they will be applied to the Trove when it next undergoes a borrower operation, a redemption, or a liquidation.
function sendETHToActivePool(uint _amount) external override {
_requireCallerIsTroveManager();
address activePool = activePoolAddress; // cache to save an SLOAD
ETH = ETH.sub(_amount);
emit DefaultPoolETHBalanceUpdated(ETH);
emit EtherSent(activePool, _amount);
(bool success, ) = activePool.call{ value: _amount }("");
require(success, "DefaultPool: sending ETH failed");
}
CollSurplusPool.sol
— :
holds the ETH surplus from Troves that have been fully redeemed and from Troves with an ICR > MCR that were liquidated in Recovery Mode.
Sends the surplus back to the owning borrower, when told to do so by
BorrowerOperations.sol
.
function accountSurplus(address _account, uint _amount) external override {
_requireCallerIsTroveManager();
uint newAmount = balances[_account].add(_amount);
balances[_account] = newAmount;
emit CollBalanceUpdated(_account, newAmount);
}
function claimColl(address _account) external override {
_requireCallerIsBorrowerOperations();
uint claimableColl = balances[_account];
require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");
balances[_account] = 0;
emit CollBalanceUpdated(_account, 0);
ETH = ETH.sub(claimableColl);
emit EtherSent(_account, claimableColl);
(bool success, ) = _account.call{ value: claimableColl }("");
require(success, "CollSurplusPool: sending ETH failed");
}
GasPool.sol
—:
holds the total LUSD liquidation reserves.
LUSD is moved into the
GasPool
when a Trove is opened and moved out when a Trove is liquidated or closed.
Liquidations
Liquity mechanism for liquidation consists on offset the liquidated debt with Stability Pool and then if not enough, redistribute the remaining debt over all active Troves ordering by collateral amounts.
Liquidations only take place under two circumstances:
when a Trove falls below the minimum collateral ratio of 110%
if the system is in Recovery Mode
The owner of a liquidated Trove is allowed to keep the LUSD they were loaned, but will still lose money since they won’t receive their collateral back (which will always be higher than the price of the loan).
There are also two different liquidation modes in Liquity:
Normal mode.
function _liquidateNormalMode(
IActivePool _activePool,
IDefaultPool _defaultPool,
address _borrower,
uint _LUSDInStabPool
) internal returns (LiquidationValues memory singleLiquidation)
{
[...]
(singleLiquidation.entireTroveDebt, singleLiquidation.entireTroveColl, vars.pendingDebtReward, vars.pendingCollReward) = getEntireDebtAndColl(_borrower);
_movePendingTroveRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
_removeStake(_borrower);
[...]
(singleLiquidation.debtToOffset, singleLiquidation.collToSendToSP, singleLiquidation.debtToRedistribute, singleLiquidation.collToRedistribute) = _getOffsetAndRedistributionVals(singleLiquidation.entireTroveDebt, collToLiquidate, _LUSDInStabPool);
_closeTrove(_borrower, Status.closedByLiquidation);
[...]
}
Recovery mode. It is triggered if the total CR of the Liquity system(TCR) is below the Critical system Collateral Ratio (CCR, 150%).
Who can liquidate Troves?
Anybody can liquidate a Trove as soon as it drops below the Minimum Collateral Ratio of 110%
. The initiator receives a gas compensation (200 LUSD
+ 0.5%
of the Trove's collateral) as a reward for this service.
Redemptions
Redemption is the process of exchanging LUSD for ETH at face value as if 1 LUSD is exactly worth $1. That is, for x LUSD you get x Dollars worth of ETH in return.
Users can redeem their LUSD for ETH at any time without limitations. However, a redemption fee might be charged on the redeemed amount.
What are LUSD redemptions?
LUSD redemptions are one of Liquity’s most unique features. Simply put: The redemption mechanism gives LUSD holders the ability to redeem LUSD at face value for the underlying ETH collateral at any time.
TroveManager.sol
The logic for redemption is located in TroveManager.sol where it starts to search the first Trove whose ICR
is bigger than or equal to MCR
(110%
).
function redeemCollateral(
uint _LUSDamount,
address _firstRedemptionHint,
address _upperPartialRedemptionHint,
address _lowerPartialRedemptionHint,
uint _partialRedemptionHintNICR,
uint _maxIterations,
uint _maxFeePercentage
)
external
override
{
[...]
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);
}
}
[...]
}
Would you like to learn to analyze projects in such a way?
I started a while ago analyzing DeFi protocols as I had a breakthrough during the second part of the Smart Contract Hacking course (Check out my article about it).
You can make use of this limited $50 discount I managed to get in order to finally get into this the right way.
Smart Contracts Hacking Course
I think it is super useful to make these analyses as it helps understand DeFi protocols before auditing them.
You will find a complete guide to boost your Web3 Security skills and start learning and practicing with important DeFi protocols.
Besides that, you will get to be surrounded by a community of people in the same situation as you are and eager to work towards the same goal.