A vulnerability has been identified in the rToken::calculateDustAmount . The issue arises due to rounding errors in the rayDiv and rayMul operations, which result in a non-zero dust amount even when it should theoretically be zero. This discrepancy could lead to unintended behavior, such as incorrect dust calculations or potential exploitation by malicious actors.
rToken::calculateDustAmount contains the following code:
Rounding Errors in rayDiv and rayMul:
The rayDiv and rayMul operations involve division and multiplication with large numbers (scaled by 1e27). These operations truncate fractional parts, leading to precision loss.
For example:
rayDiv(a, b) truncates the result of (a * RAY) / b.
rayMul(a, b) truncates the result of (a * b) / RAY.
Dust Calculation:
The dust amount is calculated as:
solidity
Copy
dustAmount = contractBalance - totalRealBalance;
Due to truncation in rayDiv and rayMul, contractBalance and totalRealBalance may not match exactly, resulting in a small positive dust amount even when it should be zero.
Example
Given assetBalance = 100000000009115210018n
normalizedIncome = 1000000000036460840071145071n
totalSupply = 100000000000000000000n
contractBalance Calculation:
contractBalance = assetBalance.rayDiv(normalizedIncome)
= (100000000009115210018 * 1e27) / 1000000000036460840071145071
≈ 100000000005469126011n (truncated)
totalRealBalance Calculation:
totalRealBalance = totalSupply.rayMul(normalizedIncome)
= (100000000000000000000 * 1000000000036460840071145071) / 1e27
≈ 100000000003646084007n (truncated)
Dust Amount:
dustAmount = contractBalance - totalRealBalance
= 100000000005469126011 - 100000000003646084007
= 1823042004n
This non-zero dust amount is a result of truncation in rayDiv and rayMul
As a result, when rToken::transferAccruedDust is called to transfer the accrued dust to a specified, there will be a small positive amount of dust left and when rToken::calculateDustAmount is called again after rToken::transferAccruedDust, there will be leftover dust calculated when the amount of dust should be 0. As a result, the next time dust is transfered out of the rToken, a small amount of the asset token will be transferred. Over time, this function called repeatedly will slowly drain asset tokens in the rToken contract that are needed to fulfill user withdrawals.
This test was run in LendingPool.test.js in the "Full Sequence" describe block
See results from test:
Note: To get the above result, the first time I ran the test, it produced a 'No Dust' error and then after running the exact same test again, this the result that occurs and when using chisel to calculate this , it checks out that there are rounding errors so if you come across the NoDust custom error, run the test again with no changes and you will get the above result
Incorrect Dust Calculations:
The rounding errors cause the dust amount to be non-zero even when it should theoretically be zero. This could lead to incorrect dust calculations and unintended behavior in the contract.
Loss of Funds:
If the dust amount is transferred out of the contract, the rounding errors could result in a small but continuous loss of funds.
Manual Review, Hardhat
Add a small tolerance to handle rounding errors. For example:
Avoid Unnecessary Operations: If possible, simplify the dust calculation to minimize the number of operations that can introduce rounding errors. For example, use scaledBalanceOf instead of balanceOf and rayDiv.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.