4

I have a question about the math in Aave V2 that performs the update liquidity and borrow index. This question was also asked and unanswered in the Aave Governance Forum.

I see calculateLinearInterest for the liquidityIndex using the liquidityRate. And I see calculateCompoundInterest for the borrowIndex using the borrowRate. Doesn't this create some dust that won't be accounted for since the compound interest will create a little more interest on the debt than the linear interest will create on the liquidity supplied?

enter image description here

I'm asking because I was using this same mechanism in a contract I created and I have some dust left over. I was wondering if anyone here in the Ethereum SE community might be able to help me understand how this works out.

It's here in Github protocol-v2 repo I was looking at, pictured above: https://github.com/aave/protocol-v2/blob/ce53c4a8c8620125063168620eba0a8a92854eb8/contracts/protocol/libraries/logic/ReserveLogic.sol#L334

2 Answers 2

3
+50

I investigated this a few month ago.

Before I get into too many details, there's one important thing to consider about different cases of interest rates inside of Aave. We essentially have 2 cases for the liquidity rate being inaccurate:

  1. The actual earning of the debt are actually higher than the liquidity rate.
  2. The actual earnings of the debt are actually lower than the liquidity rate.

Because the liquidity index is calculated with linear interest, and the debt is calculated with compound interest, we end up in scenario 1 (Compound interest will outpace linear interest. Also, look below for details on how liquidity interest rate is calculated from the 2 debt interest rates, its essentially just the combination of the 2 inside the default rates strategy). This means that over time, the amount of liquidity in the pool can start to exceed what the pool thinks it has available for liquidity. Interestingly, this actually doesn't cause any problems for the Aave system. It's inefficient yes, but the pools having slightly more money in them than they think they do does not cause any problems. Essentially, the depositors are earning a bit less than the borrowers are paying. That is not catastrophic overall though, because all depositors will always be able to withdraw all of their money.

The other scenario, scenario 2 where the liquidity index outpaced the borrow rate is far more catastrophic. Over time, depositors could end up thinking they are owed more token than the contract has liquidity for them. That would mean that if everyone tried to withdraw, some depositors would not get all their money back from the Aave contracts. This would be the scenario where the depositors think they are earning more than the borrowers are paying. That is really bad, since eventually the difference can lead to some depositors not being paid back if everyone withdraws.

So to sum up, the liquidity rate in Aave does not actually match the debt rate. However, this is ok because when the liquidity rate is calculated with linear interest, and debt is calculated with compound interest, we can be sure the liquidity rate will always be equal to or below the debt rate. This means Aave reserves can accrue a slight excess tokens over time. This however, does not cause any problems for the system other than introducing a slight capital ineffeciency.

One thing to note about this is that the liquidity rates gets compounded every time there is an interaction with that reserve. So, with the popularity of Aave, there are enough interactions to keep the liquidity interest compounding pretty often, meaning that the divergence of the rates is relatively low. I believe I calculated this once and found the effect would be relatively small for a long time, but sorry I don't have that available. You might see the problem become more pronounced in Aave forks where there are few interactions with the system.

Also, remember that is is always possible for the Aave team to fix any divergence in the rates and liquidity/debt indexes by updating the interest rate strategy contact, or updating the contracts themselves since they are upgradable. But overall, the worst problem that these diverging rates cause is some capital inefficiency, so long as we can reason that the liquidity rate stays below the debt rate.

I think the reason these rates were done differently may have been to save gas, but I honestly am not sure. Maybe also to have a buffer between debt and liquidity just for extra safety. However, hopefully all of that explanation helps you understand why this is overall fine for the Aave system.


Now, related to the topic I would like to mention one more very important mechanism built into Aave interest rates calculations.

Take a look at how the interest rates are calculated inside the default interest rates strategy. (https://github.com/aave/protocol-v2/blob/ce53c4a8c8620125063168620eba0a8a92854eb8/contracts/protocol/libraries/logic/ReserveLogic.sol).

    vars.currentLiquidityRate = _getOverallBorrowRate(
      totalStableDebt,
      totalVariableDebt,
      vars
        .currentVariableBorrowRate,
      averageStableBorrowRate
    )
      .rayMul(vars.utilizationRate)
      .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(reserveFactor));

First off, we can see that the Liquidity Rate is calculate based on the overall rate, the utilization, and the reserve factor. No surprise there. However, what is very interesting is how the overall borrow rate is calculated, since we are combining the stable debt rate and the variable debt rate.

  function _getOverallBorrowRate(
    uint256 totalStableDebt,
    uint256 totalVariableDebt,
    uint256 currentVariableBorrowRate,
    uint256 currentAverageStableBorrowRate
  ) internal pure returns (uint256) {
    uint256 totalDebt = totalStableDebt.add(totalVariableDebt);

    if (totalDebt == 0) return 0;

    uint256 weightedVariableRate = totalVariableDebt.wadToRay().rayMul(currentVariableBorrowRate);

    uint256 weightedStableRate = totalStableDebt.wadToRay().rayMul(currentAverageStableBorrowRate);

    uint256 overallBorrowRate =
      weightedVariableRate.add(weightedStableRate).rayDiv(totalDebt.wadToRay());

    return overallBorrowRate;
  }

So, interestingly, the way that Aave combines the stable and variable debt interest rates is also not mathematically perfect. They are combined using a weighted average interest, or also known as Weighted Average Cost of Capital aka WACC in the traditional finance world.

So, this mechanism begs a really important question: Are we sure that the WACC will always be lower or equal to the independent interest rates compounding individually? Because, if the approximation of the borrow rate is higher than the actual borrow rate, we could potentially run into the same problem stated above in scenario number 2 where depositors think they are earning more than the debt is actually accruing interest.

I actually spent a lot of time trying to figure out the math for this myself, its not as straightforward as you might think. However I did manager to find the answer. Essentially according to a mathematical theorem, we can prove it, and the details can be found in this math stack exchange post: https://math.stackexchange.com/questions/4560984/is-the-weighted-average-interest-rate-aka-wacc-strictly-less-than-or-equal-to.

Let me know if you have any questions or want further elaboration on anything, I know that is a wall of text and probably a lot to digest!

0

In Aave V2, they use different interest rate models for lending and borrowing:

calculateLinearInterest is used for liquidity, and it calculates interest linearly. This means that the interest earned on supplied liquidity is simply added to the liquidity index without compounding.

calculateCompoundInterest is used for borrowing, and it calculates interest in a compounded manner. This means that the interest accrued on borrowed funds is added to the borrow index, and over time, it will compound, resulting in borrowers owing more.

As you correctly pointed out, this can lead to a situation where the borrow index grows faster than the liquidity index, potentially leaving some "dust" behind.

The Aave protocol aims to balance the interests of lenders and borrowers, and this difference in interest calculation models is by design. It's essential for protocol sustainability and risk management. However, it can lead to the scenario you described, where small amounts of dust might accumulate over time.

To address this, you may need to periodically "sweep" or clean up the dust in your contract by executing a transaction that claims these small amounts of leftover interest and redistributes them. Keep in mind that the specific implementation details would depend on your contract and the tokens involved.

Not the answer you're looking for? Browse other questions tagged or ask your own question.