Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Incorrect calculation of accrued dust amount

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) {
// Calculate the actual balance of the underlying asset held by this contract
@> uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this)).rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
// 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());
// 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;
}

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 () {
// @audit PoC incorrect dust amount calculation
// deposit nft and borrow
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)
// let interest accrued
await ethers.provider.send("evm_increaseTime", [365 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine", []);
await lendingPool.connect(user1).updateState();
// repay all debt
let totalDebt = await debtToken.balanceOf(user1.address)
await lendingPool.connect(user1).repay(totalDebt);
// withdraw all supplied reserve token
let totalSupplied = await rToken.balanceOf(user2.address)
await lendingPool.connect(user2).withdraw(totalSupplied);
// dust should be the remaining `reserve.totalLiquidity`
let dust = (await lendingPool.reserve())[3];
// onchain calculation
let calculateDustAmount = await rToken.calculateDustAmount();
// this check fails
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

  • Wrong amount of dust token to be transferred

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;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RToken::calculateDustAmount incorrectly applies liquidity index, severely under-reporting dust amounts and permanently trapping crvUSD in contract

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.