LendingTerm Inconsistency between debt ceiling as calculated in borrow() and debtCeiling()
Lines of code
Vulnerability details
Impact
There is an inconsistency in the calculation of the debtCeiling between the borrow() function and the debtCeiling() function. This not only leads to operational discrepancies but also impacts liquidity utilization. Specifically, the more restrictive debtCeiling in the borrow() function results in underutilized liquidity, which in turn could lead to missed profit opportunities for lenders.
Proof of Concept
There is an inconsistency in the way borrow() calculates a much smaller value for debtCeiling than the actual debtCeiling() function. This renders this check useless since borrow prevents issuance from going even close to the actual debt ceiling.
solidityuint256 debtCeilingAfterDecrement = LendingTerm(gauge).debtCeiling(-int256(weight)); require(issuance <= debtCeilingAfterDecrement, "GuildToken: debt ceiling used");
Let's take the parameters below to illustrate the issue and follow along the two calculations:
| Parameter | Value | |------------------------|------------------------------------------------| | Issuance | 20,000 | | Total Borrowed Credit | 70,000 (5k when we call borrow + 65k old loans)| | Total Weight | 100,000 | | Gauge Weight | 50,000 | | Gauge Weight Tolerance | 60% (1.2e18) |
1. borrow() function's calculation method:
- This function calculates the debtCeiling using a simpler formula:
$$ \begin{align*} \text{debtCeiling} &= \frac{{(\text{Gauge Weight} \times (\text{Total Borrowed Credit} + \text{Borrow Amount}))}}{{\text{Total Weight}}} \times \text{Gauge Weight Tolerance} \end{align*} $$
- Applying the provided parameters (Gauge Weight: 50,000, Total Borrowed Credit: 70,000, Total Weight: 100,000, and Weight Tolerance: 1.2), the resulting debtCeiling is 42,000.
2. debtCeiling() function's calculation method:
- The formula used here is more complex so we will it break it down below:
$$ \text{debtCeiling} = \left( \left( \left( \frac{\text{Total Borrowed Credit} \times (\text{Gauge Weight} \times 1.2e18)}{\text{Total Weight}} \right) - \text{Issuance} \right) \times \frac{\text{Total Weight}}{\text{Other Gauges Weight}} \right) + \text{Issuance} $$
- This method involves several intermediate steps to arrive at the debtCeiling. The process includes:
- Calculating the tolerated gauge weight.
- Determining the initial debt ceiling before any new borrowings.
- Computing the remaining debt ceiling after factoring in current issuance.
- Establishing the weight of other gauges.
- Determining the maximum borrowable amount.
- Adding the current issuance to the maximum borrow to get the final debtCeiling.
toleratedGaugeWeight
$$ \begin{align*} \text{toleratedGaugeWeight} &= \frac{{\text{gaugeWeight} \times \text{gaugeWeightTolerance}}}{{1e18}} \ &= \frac{{50000e18 \times 1.2e18}}{{1e18}} \ &= 60000e18 \end{align*} $$
debtCeilingBefore
$$ \begin{align*} \text{debtCeilingBefore} &= \frac{{\text{totalBorrowedCredit} \times \text{toleratedGaugeWeight}}}{{\text{totalWeight}}} \ &= \frac{{70000e18 \times 60000e18}}{{100000e18}} \ &= 42000e18 \end{align*} $$
remainingDebtCeiling
$$ \begin{align*} \text{remainingDebtCeiling} &= \text{debtCeilingBefore} - \text{issuance} \ &= 42000e18 - 20000e18 \ &= 22000e18 \end{align*} $$
otherGaugesWeight
$$ \begin{align*} \text{otherGaugesWeight} &= \text{totalWeight} - \text{toleratedGaugeWeight} \ &= 100000e18 - 60000e18 \ &= 40000e18 \end{align*} $$
maxBorrow
$$ \begin{align*} \text{maxBorrow} &= \frac{{\text{remainingDebtCeiling} \times \text{totalWeight}}}{{\text{otherGaugesWeight}}} \ &= \frac{{22000e18 \times 100000e18}}{{40000e18}} \ &= 55000e18 \end{align*} $$
debtCeiling
$$ \begin{align*} \text{debtCeiling} &= \text{issuance} + \text{maxBorrow} \ &= 55000e18 + 20000e18 \ &= 75000e18 \end{align*} $$
With the same parameters, this method results in a debtCeiling of 75,000.
The lower debtCeiling set by the borrow() function (42,000) significantly restricts the amount that can be borrowed compared to what is actually permissible as per the debtCeiling() function (75,000).
This discrepancy leads to a situation where a portion of the liquidity remains unused. In a lending scenario, unused liquidity equates to lost income opportunities for lenders, as these funds are not being loaned out and thus not generating interest.
Tools Used
Manual review.
Recommended Mitigation Steps
Unify the debtCeiling calculation method which is used across the protocol.
Assessed type
Error
