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.