Summary
The function RToken::calculateDustAmount()
calculates the dust in the reserve, which is accrued because of the difference interest calculation between liquidity and debt. However, the function fails to calculate the amount accurately.
Vulnerability Details
The accrued dust amount is the remaining reserve token after subtract the contract's reserve balance by the total real obligations to the token holders, such that dust = contract_reserve_balance - total_supply
because totalSupply()
is total obligations to the token holders. Besides, the accrued dust can be the liquidity after all debt is repaid and all supplied funds are withdrawn
However, the function implements it as contract_reserve_balance / normalized_income - total_supply * normalized_income
. This can cause the calculation of dust accrued inaccurate. As a result, the flow of transferring dust can be unexpected.
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;
}
PoC
Add test to file test/unit/core/pools/LendingPool/LendingPool.test.js
describe("Deposit and Withdraw", function () {
...
it("incorrect dust amount calculated", async function () {
const depositAmount = ethers.parseEther("80");
const tokenId = 1;
await raacNFT.connect(user1).approve(lendingPool.target, tokenId);
await lendingPool.connect(user1).depositNFT(tokenId);
await lendingPool.connect(user1).borrow(depositAmount)
await ethers.provider.send("evm_increaseTime", [365 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine", []);
await lendingPool.connect(user1).updateState();
let totalDebt = await debtToken.balanceOf(user1.address)
await lendingPool.connect(user1).repay(totalDebt);
let totalSupplied = await rToken.balanceOf(user2.address)
await lendingPool.connect(user2).withdraw(totalSupplied);
let dust = (await lendingPool.reserve())[3];
let calculateDustAmount = await rToken.calculateDustAmount();
expect(calculateDustAmount).to.eq(dust)
});
Run the test and console shows:
LendingPool
Deposit and Withdraw
1) incorrect dust amount calculated
0 passing (2s)
1 failing
1) LendingPool
Deposit and Withdraw
incorrect dust amount calculated:
AssertionError: expected 383030968547912993 to equal 413673446031746032.
+ expected - actual
-383030968547912993
+413673446031746032
Impact
Tools Used
Manual
Recommendations
function calculateDustAmount() public view returns (uint256) {
// Calculate the actual balance of the underlying asset held by this contract
- uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this)).rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
+ uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this));
// Calculate the total real obligations to the token holders
uint256 currentTotalSupply = totalSupply();
// Calculate the total real balance equivalent to the total supply
- uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome());
+ uint256 totalRealBalance = currentTotalSupply;
// All balance, that is not tied to rToken are dust (can be donated or is the rest of exponential vs linear)
return contractBalance <= totalRealBalance ? 0 : contractBalance - totalRealBalance;
}