Core Contracts

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

Accidental Locking of RToken in LendingPool are Irrecoverable

Summary

When RToken are mistakenly transferred into the LendingPool contract, they become permanently locked. The contract’s rescue function explicitly prevents retrieval of these tokens, resulting in an irreversible loss of the token.

Vulnerability Details

Root Cause: The rescueToken function in the LendingPool contract prevents the rescue of tokens whose address matches reserve.reserveRTokenAddress (i.e. the RToken). As a result, any RTokens accidentally sent to the contract cannot be withdrawn.

If a user mistakenly sends RTokens directly to the LendingPool contract address (instead of interacting via the designated deposit functions), the tokens become trapped since the rescue function will revert any attempt to recover them.

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

PoC:

Paste into LendingPool.test.js and run with npx hardhat test test/unit/core/pools/LendingPool/LendingPool.test.js --show-stack-traces:

describe.only("[H] Accidental Locking of RToken in LendingPool Contract Causes Irrecoverable Funds Loss", function () {
it("should permanently lock RToken tokens sent directly to the LendingPool", async function () {
// Arrange: simulate accidental transfer of RToken tokens to the LendingPool contract.
// Assume user1 already holds some RToken tokens.
const transferAmount = ethers.parseEther("10");
await crvusd.mint(user1.address, transferAmount);
await crvusd.connect(user1).approve(lendingPool.target, transferAmount);
await lendingPool.connect(user1).deposit(transferAmount);
//Accidental Transfer
await rToken.connect(user1).transfer(lendingPool, transferAmount);
// Verify that the LendingPool now holds the accidentally transferred RToken tokens.
const lockedBalance = await rToken.balanceOf(lendingPool);
expect(lockedBalance).to.equal(transferAmount);
// Act & Assert: Attempting to rescue RToken tokens should revert,
// because rescueToken explicitly prevents retrieval of the main asset.
await expect(
lendingPool
.connect(owner)
.rescueToken(rToken, owner.address, transferAmount)
).to.be.revertedWith("Cannot rescue RToken");
});
});

Impact

Permanent loss of the user's RToken. Destabilize the debt calculation that RToken is based on.

Tools Used

Manual review, Hardhat

Recommendations

Modify the rescue function to allow recovery of accidentally sent RTokens, perhaps by requiring additional authentication or confirmation steps to prevent misuse, while still protecting against unauthorized recovery.

Updates

Lead Judging Commences

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