Modern Stablecoins, How They're Made: Liquity V2

Author(s): Sergey Boogerwooger, Konstantin Nekrasov
Security researcher(s) at MixBytes
Intro
Liquity V2 is an algorithmic stablecoin protocol that builds upon the ideas of the Liquity V1 protocol, allowing users to mint the LUSD stablecoin using ETH as collateral. Its defining features include interest-free borrowing and a low collateral ratio.

Liquity V2 enhances the capabilities of LUSD by introducing a new stablecoin, BOLD, which offers increased functionality for users. These include support for new collateral types like Liquid Staking Tokens (LSTs), integrated BOLD staking, user-defined interest rates, a reduced role of governance, and several other innovations that improve the protocol's flexibility and decentralization. As mentioned in one of our previous articles, modern protocols aim to create less-governed, more permissionless markets in DeFi to promote greater decentralization. Liquity V2 pursues these goals by minimizing reliance on centralized assets like USDC/USDT, reducing governance, employing immutable contracts, and implementing unique stabilization mechanisms.

These mechanics are worth understanding for DeFi developers. To begin our review of algorithmic stablecoin protocols, we’ve chosen Liquity V2 as an excellent example. Let’s explore how Liquity V2 works. Dive in!
Higher-level review
The basic lifecycle of BOLD is similar to that of other stablecoins:

  1. Borrowers - Borrowers take out loans using ETH/LST tokens as collateral, mint BOLD stablecoins, and see their debt continuously increase. They either repay their debt or it is paid by Redeemers/Liquidators.
  2. Stakers – Stakers lock up BOLD tokens and earn yield from the increasing debt of Borrowers.
  3. Redeemers/Liquidators – Redeemers and Liquidators monitor Borrowers' positions and exchange BOLD tokens for collateral using two mechanisms: redemption and liquidation.

The core concept behind the Liquity V2 peg stabilization mechanism is based on variable borrowing interest rates set by the users themselves. Positions with lower interest rates generate less yield for BOLD stakers, and if there are many such "low-rate" positions, the demand for BOLD (and its price) decreases. When the price drops below $1, "redemptions" are triggered. These redemptions enable BOLD to be exchanged for ETH/LST at a fixed price of $1, providing a profit opportunity when the price is below $1. Redemptions must begin with the position carrying the lowest interest rate, removing those positions from the market:

Eliminating these "low-rate" positions increases the yield for BOLD stakers, which, in turn, boosts demand for BOLD (and its market price). As the price of BOLD rises, the risks associated with redemptions decrease, allowing users to borrow BOLD at lower interest rates. This creates a self-reinforcing cycle:

The stabilization process is explained in greater detail in the whitepaper.

Liquity V2 also incorporates numerous other mechanisms, such as isolated pools for different operations (e.g., handling surplus amounts, gas compensation, and token redistribution), limits on interest rate adjustment frequency, "cooldown" redemption fees, automatic shutdown of collateral branches in response to issues with oracles or collateralization ratios, and additional innovative features.
Let’s explore how it’s all built.
Project structure
We will analyze the BOLD repository, which contains the full codebase (and a helpful overview) of the BOLD stablecoin:

BOLD is a multi-collateral protocol, with each collateral asset (hardcoded at deployment) having its own dedicated set of contracts. These contracts handle all borrowing-related logic, including the creation and closure of collateral-debt positions (called Troves) and their groupings (Batches), liquidation and redemption processes, fee management, and the accumulation of "intermediate" balances across various pools.

The combination of multiple collateral branches is managed through the main CollateralRegistry contract, whose primary function is handling the redemption process across all branches (liquidations are performed independently within the branches).

Each collateral branch operates with its own collateral asset (ETH or LST). It contains the TroveManager contract, which handles operations with Troves (including liquidations) organized in a sorted list. The BorrowerOperations contract provides an interface to all external functions within a given collateral branch.

Users create, close, and adjust their collateral-debt positions (Troves) by depositing collateral and minting BOLD stablecoins. They can select their own borrowing interest rate, keeping in mind that a lower rate leads to a higher risk of the Trove being redeemed. Accrued borrowing interest is compounded and then distributed at a 75/25 ratio to the BOLD stakers and to the InterestRouter, which incentivizes BOLD liquidity providers on external DEXes.
If a Trove has a low interest rate, it becomes subject to redemption. If a Trove becomes undercollateralized, it is subject to liquidation. This creates a challenge: the liquidator needs to obtain BOLD tokens to pay off the user's debt. This demand for BOLD tokens pushes its price up, making collateral cheaper and leading to more undercollateralized positions. To address this, during the liquidation process, Liquity V2 uses staked BOLD tokens first. These tokens are not present on the market, so operations involving them do not affect the market price of the BOLD token. Staked BOLD tokens are burned, while collateral is added to the "staking" pool (StabilityPool) instead. As a result, BOLD stakers end up with fewer BOLD tokens but more collateral owed. Only when the StabilityPool is fully depleted do liquidators turn to the BOLD tokens available on the market. This multi-step liquidation process is a unique feature of Liquity V2.

This structure of redemptions, liquidations, and staking requires Liquity V2 to have these "collateral branch" packs of contracts, which contain several pools for different purposes: staking, token redistribution, collateral surplus, and gas compensation for liquidators. We will review these in detail later.

Now, let's continue our technical review and examine the BoldToken contract.
BOLD Token
The BOLD token is implemented in BoldToken.sol and follows the standard ERC20 interface, with the added permit function. It supports all standard ERC20 functions, with additional functionality, including:

  • The mint() function, which can only be called by the borrowerOperations or ActivePool contracts.
  • The burn() function, callable only by the collateralRegistry, borrowerOperations, troveManager, or stabilityPool contracts.
  • The sendToPool()/returnFromPool() functions, which enable sending or retrieving BOLD tokens to/from the StabilityPool. These can only be called by Stability Pool contracts.

The implementation of the BOLD token intentionally keeps the ERC20-related functionality simple to ensure the token remains efficient and accessible for everyday use.
Collateral Branch
A collateral branch consists of a set of contracts that operate with a single type of collateral. Each collateral branch includes the following components:

TroveManager: A contract responsible for managing users' positions (Troves) and groups of Troves (Batches) while tracking collateral amounts, debt, staked BOLD tokens, and interest rates on a per-user basis. TM also oversees redemptions and liquidations.
BorrowerOperations: A contract that provides functions for managing Troves and Batches, such as their creation, updates, or removal, adding collateral, drawing debt, modifying the interest rate, grouping into Batches, and other operations.
ActivePool: A contract that holds the balances of collateral and BOLD tokens associated with Troves and Batches.
StabilityPool: A contract that holds users' stakes, holding BOLD tokens and collateral seized from liquidations, while accumulating interest for stakeholders.
Additional contracts:
DefaultPool: A contract holding "intermediate" balances of BOLD tokens and collateral during liquidations. It temporarily stores "pending" amounts of BOLD and collateral to be redistributed among Troves and Batches.
CollSurplusPool: A contract that holds surplus collateral resulting from liquidations. Trove owners can reclaim this surplus if any is left after the liquidation.
GasPool: A contract holding a balance of WETH contributed by borrowers to cover potential gas costs for the liquidation of their Troves.
First, we will review the TroveManager.sol contract, which manages user positions and facilitates redemptions and liquidations, the main stabilization mechanisms of the protocol.
TroveManager
User positions managed by TroveManager contract are represented as NFTs (example in TroveNFT.sol). Strict, immutable collateral ratio restrictions apply to these positions, ensuring that borrowing or collateral withdrawing is prohibited when the collateral ratio reaches specified thresholds.
The core Trove struct contains the values collateral, debt, and stake, which represent deposited collateral, borrowed debt, and stake in the StabilityPool, respectively. Operations involving "Collateral <-> BOLD" in Liquity V2 can affect all three values at the same time.

The Trove struct also includes other important attributes, such as the last update times (used for calculating accrued interest), the annualInterestRate (user-selected), and batchDebtShares — which represent the Trove's share in a larger entity called the Batch. A Batch groups multiple Troves that share the same interest rate. Batches offer benefits to Trove owners who prefer not to manage their interest rates individually, allowing them to delegate this responsibility to a batch manager. The batch manager is a trusted address that can adjust the interest rate for all Troves within the Batch and receives batch management fees. An example of Batch usage can be found in the _getLatestTroveDataFromBatch()) function. This function extracts data not from a single Trove but uses trove.batchDebtShares to calculate the Trove's absolute debt and the associated batch management fees.

Another important place to mention is handling of the collteral branch shutdown (will be discussed later) in Trove’s interest rate calculations. If a branch is shut down, the protocol needs to use the correct interest period for the Trove's interest accrual.

The primary functions of the TroveManager are redeemCollateral() and _liquidate(), but it is practical to examine these after discussing other contracts involved in borrowing operations and collateral pools.
"Zombie" Troves
In Liquity V2, Troves with a debt below MIN_DEBT (2000 BOLD) become "zombie" Troves. The minimum borrowing amount is thus set to MIN_DEBT.

"Zombie" Troves are mainly created after redemptions, which do not "close" Troves in the same way as liquidations. From a lending perspective, redemptions act as "liquidations" that target "healthy" positions. Leaving Troves open after redemption ensures that redemptions do not lower the TCR. Additionally, active "small" Troves present a potential griefing attack, where attackers clog the sorted list with tiny debts. To address this, Troves redeemed below MIN_DEBT are marked as unredeemable, removed from the sorted list, and can only become redeemable again if the owner increases their debt above MIN_DEBT. The reasoning for allowing these unredeemable Troves is detailed here.
BorrowerOperations
The next contract to review is BorrowerOperations, whose main functions involve the creation, modification, and closure of Troves and Batches. Liquity V2 offers a wide range of features for working with Troves; here, we will focus on the most important aspects.

First, let’s examine the security shutdown mechanism, which is controlled by immutable thresholds: CCR, SCR, and MCR, representing Critical, Shutdown, and Minimal collateral ratios:

  • Reaching the SCR (shutdown) collateral ratio switches BorrowerOperations to shutdown mode, restricting all borrowing operations and allowing only the closure of Troves
  • Reaching the CCR (critical) collateral ratio disables part of the borrowing operations (e.g., opening, adjusting, or closing Troves) but still permits changes in interest rates, debt repayment, and any adjustments that improve the collateral ratio
  • The MCR (minimal) collateral ratio is used to restrict individual Troves (in open/adjust Trove operations).

Every Trove adjustment involves obligatory checks to ensure the changes maintain individual collateral rate ICR > MCR and total branch collateral ratio TCR > CCR.

Trove creation occurs through the "standalone" openTrove() function or openTroveAndJoinInterestBatchManager(), which adds the new Trove to a Batch. Both functions ultimately call the internal _openTrove() function. Let’s explore its mechanics.

First, we need to explain what "weighted debt" means. These values are frequently encountered in Liquity V2 (e.g., here) and refer to debt estimated by applying an annual interest rate (example: 100$ with 10% interest = 110$ versus 101$ with 5% = 106.05$). This value plays an important role and is used more frequently than "raw" debt amount to determine fees and estimate Trove "value". It is updated whenever debt changes (e.g., during Trove creation, adjustments, interest rate updates, redemptions, and liquidations) and is used when calculating the aggregated average interest rate for the entire collateral branch (e.g., via the getNewApproxAvgInterestRateFromTroveChange() function).

Another important component to describe is the upfront fee, which linearly depends on the total average interest rate of the pool. Higher interest rates in a pool correspond to higher upfront fees.

Let's continue with creating a Trove. A subsequent check ensures this fee does not exceed what the user is prepared to pay (slippage protection). Next, the check for MIN_DEBT (described above).

The subsequent steps include the calculation of "weighted" debt and payment of the Batch management fee (if required), a check for the minimum collateral ratio (MCR), and a calculation of a new total collateral ratio for the entire collateral branch (which incorporates the new debt, collateral, and fees into total reserves).

Next comes the delegation of "add" and "remove" managers. Liquity V2 enables third-party Trove management, where delegated accounts can perform mirrored operations. The addManager can add collateral and repay debt (increasing the collateral ratio), while the removeManager can withdraw collateral and draw debt (decreasing the collateral ratio). This feature facilitates the integration of Liquity V2 with external position management systems.

Another crucial step is the call to the widely employed mintAggInterestAndAccountForTroveChange() function, which updates total debt-related values in the ActivePool (including direct and "weighted" sums of all Troves’ debt and collateral balances), as well as upfront fees.

The final sequence of interactions, adhering to the Checks-Effects-Interactions pattern, enables collateral pulling from the user for placement into the ActivePool, minting BOLD tokens for the user, and transferring some WETH to the GasPool to cover liquidation gas fees.

Upon completing the internal Trove-opening workflow, external functions can mint TroveNFTs, either for "standalone" or "joined batch" Troves. An example of standalone Trove minting is here. In the case of batch joining via openTroveAndJoinInterestBatchManager(), the Trove also joins a Batch. As Troves are arranged by interest rate, a Batch simply uses "head" and "tail" pointers in the sorted list (demonstrated in the insertIntoBatch() function in SortedList.sol).

The next significant function is _adjustTrove(), which operates similarly to _openTrove, but can alter collateral and debt in both directions and handle different actions on an existing Trove. This function appears in addColl(), withdrawColl(), and repayBold() functions.

The function _adjustTrove() begins with crucial checks for oracle liveness and TCR thresholds for the collateral branch. Next, it includes access control checks for Trove managers, limit checks during adjustments, and final updates to both debt and collateral. More complicated updates are required for Troves within a Batch.

The process concludes similarly to _openTrove and ends by minting interest (which is performed each time a Trove is "touched") and minting/burning/transferring appropriate BOLD and collateral token amounts.

A unique function is adjustTroveInterestRate(), which adjusts a Trove’s interest rate. This is not trivial in Liquity V2, as it requires recalculating “weighted” debt, charging an upfront fee due to debt changes, and reinserting the Trove into the sorted list based on its new position. This change applies to standalone Troves only.

Additionally, the intermediate function applyPendingDebt() applies the interest rate to a Trove’s debt. In some cases, this operation can increase the debt of “zombie” Troves, placing them back into the sorted list and removing their "zombie" status.

Closing a Trove in closeTrove() is straightforward after understanding the previous functions. This operation ends by burning BOLD tokens, withdrawing all remaining collateral, and returning gas compensation in WETH.

Other functions in BorrowerOperations handle Trove manager configurations, allowing them to manage debt, collateral, interest rate changes, or become Batch managers. Liquity V2 minimizes governance functions, and no governance-related logic is present in BorrowerOperations.
ActivePool
The ActivePool contract is responsible for holding the BOLD and collateral token balances managed by Troves. From another perspective, AP can be seen as a large "aggregation" Trove that combines all collateral and BOLD balances from all Troves.

As stated earlier, ActivePool handles receiving and sending collateral and BOLD tokens. Specifically, the key functions involve moving collateral and BOLD tokens. The sendColl() function transfers collateral externally, such as in redeemCollateral (to send collateral to redeemers) or when sending surplus collateral to the CollSurplusPool during liquidations. Conversely, collateral is received by the receiveColl() function.
Stability Pool
The StabilityPool contract is a critical component of the stabilization mechanism. This is the pool where BOLD holders stake their BOLD tokens to earn yield from interest rates and liquidations. As mentioned earlier, it is the first target for burning BOLD tokens during liquidations.

This pool plays a dual role: it implements BOLD staking functionality while enabling users to claim seized collateral from liquidations. When a Trove is liquidated, its debt is effectively removed, and an equivalent amount of BOLD is burned in the Stability Pool. At the same time, the seized collateral is transferred to the Stability Pool and must be redistributed among depositors. This process requires proportionally lowering BOLD balances of all depositors and increasing their collateral balances. Similarly, the interest earned by the pool must also be distributed among BOLD holders.

Iterating over deposits is inefficient in DeFi contexts, so Liquity V2 employs a distribution scheme explained in their paper. The key idea is that after each i-th liquidation (when totalSupply of BOLD decreases and totalSupply of collateral increases in the StabilityPool), the value of an individual deposit can be updated based on its previous value and the liquidated fraction of BOLD (amountLiquidated/totalSupply). Put simply, if the totalSupply of BOLD is reduced by 5%, then individual BOLD deposits should also decrease by the same ratio. By sequentially applying this reduction starting from an initial "zero-time" deposit, we arrive at an iterative calculation scheme:

where d₀ is the initial deposit at i=0, Qᵢ is the liquidated amount of BOLD during the i-th liquidation, and Dᵢ represents the totalSupply of BOLD at the i-th liquidation.

As a result, after each successive liquidation, we can "compound" the proportional reductions (marked in red), retaining only the d₀ value for each deposit. By also keeping the compounded decrease ratio (marked in orange), we can calculate the "current" value of a deposit at the n-th liquidation with one simple multiplication.

A similar logic is applied to seized liquidation amounts (details omitted here but explained in the paper). The final formula for acquiring collateral during the t-th liquidation is:

where d is the deposit at the t-th liquidation, S and S are liquidation sum snapshots (stored and updated per each deposit), and P is the cumulative product of the BOLD "total supply cumulative difference" (a product, described earlier).

The same methodology is used to compute BOLD yield gains, which proportionally increase BOLD deposits of stakers (utilizing another B accumulator in the snapshot).

Thus, this system ensures that each liquidation or yield event updates just a few (three) accumulators in the StabilityPool's snapshots. These accumulators are used whenever a depositor's balance is "touched" to recalculate the subsequent "anchor" values for the deposit. The scheme operates with O(1) complexity, ensuring constant gas costs for both deposit and liquidation-related operations.

The calculation of products, using values within the [0, 1] range (like 1−Q₊₁/D) in the EVM has drawbacks. Scaling accumulated products is necessary to preserve precision. Additionally, corner cases arise when the pool is empty, resulting in a zero totalSupply for BOLD. These situations require handling through the introduction of "epochs," which increment whenever the pool is entirely depleted.

The accumulators for these operations are stored in the Snapshots structs, while "per-deposit" values are maintained in three mappings.

The function getDepositorCollGain() implements the formula above for calculating the gain of seized collateral from liquidations for a given depositor. It uses the "S" part of snapshots. As can be observed, these calculations are straightforward and gas-efficient. The second function, getDepositorYieldGain(), operates similarly but uses the "B" part of snapshots.

The provideToSP() function is the BOLD "deposit" operation for a specific depositor in the StabilityPool. It collects accumulated interest from the ActivePool, calls the two functions mentioned above to determine BOLD yield and seized collateral gains, and calculates the amounts of BOLD and collateral to send to the depositor if applicable. Then, in the _updateDepositAndSnapshots() function, it updates the depositor's snapshot accumulators and transfers the "in" or "out" tokens (BOLD and collateral) while updating the total amounts of BOLD and collateral in the pool. The "withdraw" function, withdrawFromSP(), follows the same logic, collecting interest from the ActivePool, updating the depositor's snapshots, and transferring the resulting amounts to the depositor.

We cannot review every aspect of the StabilityPool's maths, covering potential edge cases, but the outcome of these calculations is the straightforward design of functions that manage stakers' BOLD and collateral yields.
DefaultPool and CollSurplusPool
These two pools serve as "intermediate" components. They are not core parts of the BOLD stabilization mechanism, but they handle BOLD and collateral amounts arising during redemptions and liquidations. These amounts must either be redistributed (DefaultPool) or temporarily held (CollSurplusPool) for later retrieval.

The implementations of these pools are straightforward. The DefaultPool simply tracks its BOLD debt, holds collateral, and transfers it to the ActivePool as needed.

The CollSurplusPool holds balances of collateral tokens, increasing them by depositing surpluses from liquidations into the balance of the Trove owner and allows owners to claim these funds later.
Risk management
Now, after reviewing the "base" contracts (TroveManager, BorrowerOperations, ActivePool, StabilityPool) along with the additional DefaultPool and CollSurplusPool, we can examine the primary stabilization mechanisms: redemptions and liquidations.
Redemption
Redemption is performed using the redeemCollateral() function. This function represents a single step within the loop of redeemCollateral() in the CollateralRegistry, where the process is executed across multiple collateral branches within each TroveManager.

In a specific collateral branch, this function iterates over multiple Troves, burning BOLD debt and redeeming collateral, aiming to fulfill the remainingBold amount specified by the redeemer. The iteration uses a sorted list of Troves, starting from those with the lowest interest rate, as outlined in the whitepaper.

The first Trove processed is the last one in the sorted list (additional logic relates to "zombie" Troves). Selecting the last Trove in the sorted list is sufficient to identify the Trove with the minimal interest rate.

Next, a loop is initiated over the sorted Troves, which is limited by _maxIterations, a parameter defined by the redeemer to prevent "out-of-gas" errors. During each iteration, the ICR (individual collateral ratio) for the Trove is calculated using the _price provided by the redeemer, ensuring the Trove's ICR does not fall below 100%. The ICR calculation is straightforward and implemented in the _computeCR() function.

If the Trove belongs to a Batch with other Troves, the Batch's interest rate is updated (only once, as all Troves within the Batch share the same rate). Finally, collateral is redeemed from the Trove by calling _redeemCollateralFromTrove(). Within this function, the redemption fee (in collateral tokens) is calculated, and the Trove's collateral and debt amounts are updated. If the Trove belongs to a Batch, the Batch is also updated. If the Trove's final debt becomes negligible, it is designated as a "zombie" and subsequently removed from the Batch.

At the end of each loop iteration, the total amounts of redeemed collateral and BOLD tokens removed are updated, along with the accumulation of prior and current debts throughout the redemption process.

After enough Troves have been redeemed, accrued interest fees are minted to the Stability Pool (75%), benefiting BOLD stakers, and to the Interest Router (25%) to incentivize BOLD pools on external DEXes. The Batch Management fee is then paid to the managers of the affected Batch.

The final step involves sending the redeemed collateral to the redeemer.
Liquidation
The second core stabilization mechanism is liquidation. Let’s examine the internal _liquidate() function, which forms the foundation of all liquidations.

First, we process any undistributed debt and collateral gain in the Trove. Then, gas compensation is provided to the liquidator. This compensation equals 1/200th of the collateral being liquidated, capped at 2 ETH (constants are defined here).

In Liquity V2, liquidations prioritize using the BOLD balance from the StabilityPool (burning BOLD in SP while transferring seized collateral to it). If the SP's BOLD balance is insufficient, BOLD tokens from the liquidator are used, with the collateral and BOLD distributed among Troves. Any remaining collateral in the liquidated Trove is transferred to the CollSurplusPool and can be claimed by the Trove's owner.

The liquidation process first determines the proportions for StabilityPool and redistribution. These amounts are calculated by evaluating the "StabilityPool" part first and then the "redistribution" part (previously reviewed in redistribution mechanics).

The next step involves closing the Trove within the _closeTrove() function. The Trove is removed from the sorted list, with additional handling for Batches or "zombie" Troves, and the totalStakes value is updated. The Trove is also deleted from the main Troves[] mapping and its related TroveNFT removed. Following this, batch management fees (if applicable) are paid.

Collateral surplus is then sent to the CollSurplusPool, which accumulates surplus collateral and enables Trove owners to claim it.

Finally, the liquidation process completes by removing the _troveId from various mappings related to Trove management.
Collateral registry
Now, we can combine collateral branches and review the CollateralRegistry contract, which gathers all collateral branches of the protocol together. It holds the configuration of multiple (up to 10) immutable addresses of collateral assets and their corresponding troveManager's.

Redeeming collateral from multiple collateral branches is the primary function of the collateralRegistry contract (liquidations are managed independently in the collateral branches). Redemption is performed in the redeemCollateral() function, which iterates over multiple troveManagers, attempting to redeem collateral assets. The function also burns the corresponding amount of BOLD tokens and updates the baseFee. The redemption amounts allocated to each collateral branch are proportional to the branch's "weight," where "weight" is its fraction of the total debt of the entire protocol. For example, if three branches have 10%, 20%, and 70% of the total debt, the total redeemable amount will be divided in the same 10%, 20%, and 70% proportions for each collateral branch. This "redemption routing" is described here.

An important value in this contract is baseFee, which is used to calculate the redemption fee applied when a redemption occurs. This fee changes with each new redemption and depends on the fraction of the collateral being removed (as described below in the "Fees" section).
Fees
The role of fees in DeFi protocols is not only to provide incentives but also to protect against certain dangerous scenarios. Liquity V2 uses different types of fees. Let’s discuss them.

The first type of fee is the "upfront fee." This fee is taken in BOLD tokens and is calculated based on debt. It is applied to each operation that increases the total debt, including _openTrove(), _adjustTrove(), and adjustTroveInterestRate(). The final operation also incurs an upfront fee because changing the interest rate alters the "weighted debt," which is used in many calculations. Moreover, in the last case, the upfront fee has a "relaxation" period of 7 days and is only applied if the change is made within this period following the last adjustment. This mechanism protects the protocol from "redemption evasion," where borrowers might temporarily change the interest rate, or close and reopen their Trove upon spotting redemption risk in the mempool.

Another type of fee is the batch management fee, which is taken in BOLD tokens by managers of specific Batches (the size of this fee is set by the managers themselves). It operates similarly to BOLD interest, calculating the annual interest rate based on the debt of a Batch's Troves. We will not delve further into this type of fee as it is straightforward.

The next fee is the redemption fee, which features an interesting relaxation scheme illustrated below:

The orange line represents the baseFee, used to calculate the amount of collateral taken as a fee during a redemption. Each blue bar represents a redemption, with its height indicating the redeemed amount. When a redemption occurs, this fee is "pushed up" and then "cools down" until the next redemption. This behavior is implemented in this function. The purpose behind this design is to deter large-scale redemptions and subsequent redemptions by imposing higher fees, thereby discouraging them. Additionally, this fee has a floor of 0.5%.
Implementation details
Like many other algorithmic stablecoins, Liquity V2 relies on price oracles (provided by Chainlink) to determine the current prices of collateral assets. These oracles are implemented under the PriceFeeds module.

A notable feature in Liquity V2 is the use of "hints" for the sorted list of Troves. All Troves are organized within a sorted list, so inserting or reinserting an item into this list requires multiple iterations, which consumes additional gas. To optimize this, users can instruct the protocol to begin the search from a specific element called a "hint" (example here). To assist users in providing a hint, the HintHelpers.sol contract includes a special view function called getApproxHint().

We almost forgot about the GasPool, but its role is simply to hold WETH, which is used to compensate gas for liquidators, as seen in the TroveManager here.
Conclusion
Liquity V2 and its BOLD stablecoin represent an intriguing project that uses ETH-pegged assets as collateral, avoiding the use of centralized USDT/USDC. Its stabilization mechanism is based on redemptions and liquidations. The smart idea underlying Liquity V2’s stabilization logic is its combination of BOLD staking, liquidations, and redemptions into a single mechanism. Here, burning BOLD and seizing collateral redistributes assets in the StabilityPool, automatically rebalancing the stablecoin and collateral amounts held by users.
The standout mechanics in Liquity V2 include:

Borrowing interest rates set by users
User Troves (collateral-debt positions) organized in a sorted list by interest rate
Redemptions when the BOLD price is below $1 (liquidations at a fixed 1-to-1 price)
ᅠ◦ Starting with Troves with the lowest interest rates, gradually removing such positions, increasing BOLD yield and demand
Liquidations when the BOLD price is above $1
ᅠ◦ Achieved by burning BOLD and adding collateral to the StabilityPool, reducing BOLD yield and demand
BOLD staking in the StabilityPool, which combines yield from borrowing interest rates and liquidations simultaneously
Combining multiple collateral branches in redemptions, distributing redemption amounts proportionally to "per-branch" debt

Liquity V2 also features a wide range of additional functionalities, such as grouping Troves into Batches, delegating management of Troves and Batches, gas compensation, automatic protection of collateralization ratios, and many other innovative solutions.
This project is undoubtedly worth studying as an exemplary implementation of an algorithmic stablecoin, which is why it has been included in our "stablecoins" series of articles. See you in our next articles!
  • Who is MixBytes?
    MixBytes is a team of expert blockchain auditors and security researchers specializing in providing comprehensive smart contract audits and technical advisory services for EVM-compatible and Substrate-based projects. Join us on X to stay up-to-date with the latest industry trends and insights.
  • Disclaimer
    The information contained in this Website is for educational and informational purposes only and shall not be understood or construed as financial or investment advice.
Other posts