Core Contracts

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

Impossible to rescue funds from `RToken` contract

Summary

The function provided by the RToken contract to rescue any ERC20 tokens that were sent to the contract by mistake is only callable by LendingPool.
However, LendingPool does not offer any function to call the RToken contract, resulting in unrecoverable funds.

Vulnerability Details

The Rtoken contract provides a function to rescue any ERC20 tokens that have been sent to the contract directly by mistake.
This function is only callable by the configured reservePool, which is ensured by the onlyReservePool modifier.
This will be LendingPool in practice.

Looking at the LendingPool contract, it comes indeed with a rescueToken function, however, that one only allows for rescuing funds from itself.
It does not provide the necessary functionality to recover funds from the RToken contract.

Here's what it looks like:

function rescueToken(address tokenAddress, address recipient, uint256 amount) external onlyOwner {
require(tokenAddress != reserve.reserveRTokenAddress, "Cannot rescue RToken");
IERC20(tokenAddress).safeTransfer(recipient, amount);
}

Notice how it uses safeTransfer to move funds from itself to the recipient, but it does not call into the RToken contract.

Impact

ERC20 tokens, that are not the configured reserve asset, which are sent to the RToken contract by mistake are not recoverable until the owner of the protocol configures a new reservePool that provides the necessary functionality

Tools Used

Manual review.

Recommendations

There's two ways to go about this:

  1. Either allow the owner of the protocol to call rescueFunds on RToken directly or

  2. Extend LendingPool, which is the expected reservePool in production, to provide the necessary function.

I'd recommend going for option 1) simply because any change of reservePool could reintroduce this issue.
Here's the necessary change:

- function rescueToken(address tokenAddress, address recipient, uint256 amount) external onlyReservePool {
+ function rescueToken(address tokenAddress, address recipient, uint256 amount) external onlyOwner {
if (recipient == address(0)) revert InvalidAddress();
if (tokenAddress == _assetAddress) revert CannotRescueMainAsset();
IERC20(tokenAddress).safeTransfer(recipient, amount);
}

Relevant links

Updates

Lead Judging Commences

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

RToken::rescueToken() can never be called

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

RToken::rescueToken() can never be called

Support

FAQs

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