Core Contracts

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

DOS to Users/ last liquidity owner when everyone is withdrawing their Funds

Summary

When User Calls to withdraw they specify a value bigger than their Balance so they can withdraw their max balance, But this is not well handled in the _ensureLiquidity call and if the right amount is not specified the call to withdraw will revert.

Vulnerability Details

Rtoken burn

/**
* @notice Burns RToken from a user and transfers underlying asset
* @param from The address from which tokens are burned
* @param receiverOfUnderlying The address receiving the underlying asset
* @param amount The amount to burn (in underlying asset units)
* @param index The liquidity index at the time of burning
* @return A tuple containing:
* - uint256: The amount of scaled tokens burned
* - uint256: The new total supply after burning
* - uint256: The amount of underlying asset transferred
*/
function burn(
address from,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256) {
if (amount == 0) {
return (0, totalSupply(), 0);
}
@audit>> supplied balance>>> uint256 userBalance = balanceOf(from);
_userState[from].index = index.toUint128();
@audit>> if(amount > userBalance){
@audit>> amount = userBalance;
@audit>> }
uint256 amountScaled = amount.rayMul(index);
_userState[from].index = index.toUint128();
_burn(from, amount.toUint128());
if (receiverOfUnderlying != address(this)) {
IERC20(_assetAddress).safeTransfer(receiverOfUnderlying, amount);
}
emit Burn(from, receiverOfUnderlying, amount, index);
return (amount, totalSupply(), amount); // Bug low wrong emission emit burned scaled , totalsuply , amount
}

The contract supports stating a large integer to withdraw the max balance of their account

but this will not work well as the last user to withdraw will be DOSed also if users use the uint256.max to withdraw , their call will revert.

see lending pool

/**
* @notice Allows a user to withdraw reserve assets by burning RTokens
* @param amount The amount of reserve assets to withdraw
*/
function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) { // no check if user is underliquidation abi we no dey liquidate token????
if (withdrawalsPaused) revert WithdrawalsArePaused();
// Update the reserve state before the withdrawal
ReserveLibrary.updateReserveState(reserve, rateData); // will this not be don ein the reserve library, we don't need this on the outside na
@audit>> what if we want to use a large amount to withdraw all >> // Ensure sufficient liquidity is available
@audit>> amount sent might not be the actual amount >> _ensureLiquidity(amount);
// Perform the withdrawal through ReserveLibrary
(uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) = ReserveLibrary.withdraw(
reserve, // ReserveData storage
rateData, // ReserveRateData storage
amount, // Amount to withdraw
msg.sender // Recipient
);
// Rebalance liquidity after withdrawal
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}

Call will revert if used by the last User or any user that uses the maxuint256

/**
* @notice Internal function to ensure sufficient liquidity is available for withdrawals or borrowing
* @param amount The amount required
*/
function _ensureLiquidity(uint256 amount) internal {
// if curve vault is not set, do nothing
if (address(curveVault) == address(0)) {
return;
}
uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// Withdraw required amount from the Curve vault
@audit>> reverts>> _withdrawFromVault(requiredAmount);
}
}

The same implementation exists in AAVE =>

function executeWithdraw(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.UserConfigurationMap storage userConfig,
DataTypes.ExecuteWithdrawParams memory params
) external returns (uint256) {
DataTypes.ReserveData storage reserve = reservesData[params.asset];
DataTypes.ReserveCache memory reserveCache = reserve.cache();
reserve.updateState(reserveCache);
uint256 userBalance = IAToken(reserveCache.aTokenAddress).scaledBalanceOf(msg.sender).rayMul(
reserveCache.nextLiquidityIndex
);
uint256 amountToWithdraw = params.amount;
@audit>> if (params.amount == type(uint256).max) {
amountToWithdraw = userBalance;
}

Impact

DOS to users that what to use uint128 or unit 256 max to withdraw their max balance. Also specifically the last user to withdraw from the vault will be DOSed.

Tools Used

Manual Review

Recommendations

NEST a check to ensure that amount if > user balance is equal to the user balance and our request is feasible to avoid unnecessary revertions.

/**
* @notice Allows a user to withdraw reserve assets by burning RTokens
* @param amount The amount of reserve assets to withdraw
*/
function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (withdrawalsPaused) revert WithdrawalsArePaused();
// Update the reserve state before the withdrawal
ReserveLibrary.updateReserveState(reserve, rateData);
++ uint256 userBalance = balanceOf(from);
++ if(amount > userBalance){
++ amountin = userBalance;
++ }
++ // Ensure sufficient liquidity is available
-- _ensureLiquidity(amount);
++ _ensureLiquidity(amountin);
// Perform the withdrawal through ReserveLibrary
(uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) = ReserveLibrary.withdraw(
reserve, // ReserveData storage
rateData, // ReserveRateData storage
amount, // Amount to withdraw
msg.sender // Recipient
);
// Rebalance liquidity after withdrawal
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!