Boost _ Folks Finance 33807 - [Smart Contract - Low] updateInterestRate uses incorrect reference of borrow interest rate to calculate deposit interest can lead to the loss of lenders unclaimed yield
Submitted on Mon Jul 29 2024 17:44:26 GMT-0400 (Atlantic Standard Time) by @nnez for Boost | Folks Finance
Report ID: #33807
Report type: Smart Contract
Report severity: Low
Target: https://testnet.snowtrace.io/address/0x96e957bF63B5361C5A2F45C97C46B8090f2745C2
Impacts:
Permanent freezing of unclaimed yield
Description
Summary
updateInterestRate
function uses an old borrow interest rate to calculate the deposit interest rate. If a block concludes with an operation that raises the borrow interest rate, the deposit interest rate and deposit interest index will not be updated accordingly. This discrepancy can ultimately result in lenders losing their lending yield.
Description
For any operation affecting the utilization ratio, updateInterestRates
function is invoked to update the borrow and deposit interest rates.
In its implementation, the function first loads the current data, including the interest rates, from poolData
. It then calculates the new variable and stable borrow interest rates using the updated utilization ratio. However, when calculating the deposit interest rate, it uses the old borrow interest rates from the cached poolData
. This results in a discrepancy between the new deposit interest rate and the old borrow interest rates.
To illustrate more on the issue, let's consider the fUSDC pool with the following initial state: fUSDC pool's setting:
Vr0 = 0.0175
Vr1 = 0.0500
Vr2 = 1
optimalUtilsation = 0.85
RR (Retention Rate) = 0.1
Formula for interest rate calculation: https://docs.google.com/document/d/1UU-zhy-Ik6h-EhKS2TvcIsd0Q377H7HKF6MGP5WdwAk/
Pool's state | |
---|---|
totalAmount | 100e6 |
totalDebt | 20e6 |
utilisationRatio | 0.2 |
borrowInterest | 0.0175 + (0.2/0.85)*0.0500=0.02926470588=2.926% |
Now, supposed that ALICE borrows another 20e6 at the end of the block. The final state would be the following:
Pool's state | |
---|---|
totalAmount | 100e6 |
totalDebt | 40e6 |
utilisationRatio | 0.4 |
borrowInterest | 0.0175 + (0.4/0.85)*0.0500=0.04102941176=4.103% |
And according to the implementation, we can calculate deposit interest from the following:
Ut * overallBorrowInterest * (1-RR)
0.4 * (40e6*0.02926470588/40e6) * (1-0.1) = 0.01053529411
Supposed that this block ends with this borrow operation (raising borrow amount), and the next block comes in next 2 seconds The interest accrue from the last block to this block would be:
deposit interest: 100e6 * 0.01053529411 * 2 / (365*86400) = 0.06681439694
borrow interest: 40e6 * 0.04102941176 * 2 / (365*86400) = 0.10408272897
We can see that accrued deposit interest is over 50% less than expected (even if we include the retention rate) compare to borrow interest. This discrepancy results in a loss of unclaimed yield for depositors/lenders.
The loss amplifies when the pool and utilisation rate grows larger.
Impact
Lenders lose their unclaimed yield if the block ends with operation that raise borrow interest rate.
Proof of concept
Proof-of-Concept
A test included in the secret gist simulates the above situation. It shows that the interest rate and accured deposit interest are different when the block ends with borrow operation compared to the block that ends with deposit (with 0 amount).
Steps
Run
forge init --no-commit --no-git --vscode
.Create a new test file,
FolksDepositInterest.t.sol
intest
directory.Put the test from secret gist in the file: https://gist.github.com/nnez/10a59d5a40ad57ca50beb6ae68ee41a9
Run
forge t --match-contract FolksLoreTest -vv
Observe that in the first case, accured deposit interest is less than the second case.
Last updated