Here:
function calculateDustAmount() public view returns (uint256) {
uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this)).rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
uint256 currentTotalSupply = totalSupply();
uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
return contractBalance <= totalRealBalance ? 0 : contractBalance - totalRealBalance;
}
Description:
The calculateDustAmount()
function incorrectly scales values when calculating dust, leading to permanent loss of protocol funds. The issue occurs because:
totalSupply()
already returns a value scaled by normalized income:
function totalSupply() public view returns (uint256) {
return super.totalSupply().rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}
Then calculateDustAmount()
scales this value again with rayMul(getNormalizedIncome())
, resulting in double scaling of the total supply.
Impact:
This double scaling causes:
Protocol funds to be permanently locked as "invisible" dust
transferAccruedDust()
fails to detect and transfer actual dust
Loss of accumulated interest and donations
Incorrect accounting of excess funds
Proof of Concept:
normalizedIncome = 1.1e27 (10% interest)
contractBalance = 1000e18
super.totalSupply() = 900e18
1. contractBalance after rayDiv:
1000e18 / 1.1e27 = 909.09e18
2. currentTotalSupply (first scaling):
900e18 * 1.1e27 / 1e27 = 990e18
3. totalRealBalance (second scaling):
990e18 * 1.1e27 / 1e27 = 1089e18
4. Dust check:
909.09e18 <= 1089e18 ? 0 : 909.09e18 - 1089e18
= 0
Actual dust = 1000e18 - (900e18 * 1.1e27 / 1e27)
= 1000e18 - 990e18
= 10e18
Recommended Mitigation:
function calculateDustAmount() public view returns (uint256) {
uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this));
uint256 currentTotalSupply = totalSupply();
return contractBalance <= currentTotalSupply ? 0 : contractBalance - currentTotalSupply;
}