Core Contracts

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

TransferAccuredDust will never work as intended.

Summary

When users supply liquidity into the Rtoken, they are minted a rebase rTOKEN which increasing over time. When the amount in the contract after all debt repayment is greater than the contract balance admin can take this dust amount without affecting other users amount but presently the total supply returns the asset value directly but we multiply this by the index again inflating the Total real amount to withdraw and disallowing the admin function from operating as it should.

Vulnerability Details

Admin calls transfer Accrued Dust

/**
* @notice Allows the Reserve Pool to transfer accrued token dust
* @dev Only callable by the Reserve Pool
* @param recipient The address to send the accrued dust to
* @param amount The requested amount to transfer (will be capped at actual dust amount)
*
* Limits transfer to actual dust amount
*/
function transferAccruedDust(address recipient, uint256 amount) external onlyReservePool {
if (recipient == address(0)) revert InvalidAddress();
@audit>> uint256 poolDustBalance = calculateDustAmount();
if(poolDustBalance == 0) revert NoDust();
// Cap the transfer amount to the actual dust balance
@audit>> uint256 transferAmount = (amount < poolDustBalance) ? amount : poolDustBalance;
// Transfer the amount to the recipient
@audit>> IERC20(_assetAddress).safeTransfer(recipient, transferAmount);
emit DustTransferred(recipient, transferAmount);
}

The dust is calculated by ensuring the amount of assets available is above the maxsupply we would pay to liquidity providers.

/**
* @notice Calculate the dust amount in the contract
* @return The amount of dust in the contract
*/
function calculateDustAmount() public view returns (uint256) {
// Calculate the actual balance of the underlying asset held by this contract
@audit>> uint256 contractBalance = IERC20(_assetAddress).balanceOf(address(this)).rayDiv(ILendingPool(_reservePool).getNormalizedIncome());
// Calculate the total real obligations to the token holders
@audit>> uint256 currentTotalSupply = totalSupply(); // BUG
// Calculate the total real balance equivalent to the total supply
@audit>>BUG>> uint256 totalRealBalance = currentTotalSupply.rayMul(ILendingPool(_reservePool).getNormalizedIncome()); // BUG Over estimation medium cause over valuation
// All balance, that is not tied to rToken are dust (can be donated or is the rest of exponential vs linear)
@audit>> return contractBalance <= totalRealBalance ? 0 : contractBalance - totalRealBalance;
}

Total supply returns the actual Amount owed in the contract but this is multiplied again with the rayMul .

E.g

  1. Total scaled balance = 1000 USD

  2. index = 1.1e27

  3. All balance = 1120 USD

  4. Total supply will return = scaled * index = 1100 USD

  5. But this is multiplied with index again

  6. Total real balance will return = 1100 * 1.1 = 1210 USD

  7. Admin is allowed to withdraw 0, 1120 - 1210 = -90 USD

/**
* @notice Returns the non-scaled total supply
* @return The non-scaled total supply
*/
function scaledTotalSupply() external view returns (uint256) {
return super.totalSupply();
}
/**
* @notice Returns the scaled total supply
* @return The total supply (scaled by the liquidity index)
*/
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
@audit>> return super.totalSupply().rayMul(ILendingPool(_reservePool).getNormalizedIncome());
}

Asset value is obtained by rayMul from scaled

While scaled is obtained from amount via rayDiv

function mint(
address caller,
address onBehalfOf,
uint256 amountToMint,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256, uint256) {
if (amountToMint == 0) {
return (false, 0, 0, 0);
}
@audit>> uint256 amountScaled = amountToMint.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();

Impact

Admin will not be able to withdraw the dust amount in the contract as long as there are Rtokens in the contract. only when total supply is 0 can admin withdraw breaking the code functionality.

Tools Used

Manual Review

Recommendations

Use and return the scaled total supply or use the valuation returned by the total supply directly.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.