Core Contracts

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

LendingPool::accrued interest from crvVault are locked forever as there is no function to retrieve them and they are not accounted

Summary

In LendingPool, users deposit crvUSD, with a buffer liquidityBufferRatio kept on the RToken contract, and the rest is staked in the curveVault

Curve vault has two tokens, crvUSD which is pegged 1:1 with the $, and scrvUSD which is a rebasing token, 1:1 with crvUSD. In LendingPool, only crvUSD value is tracked.

Vulnerability Details

All values tracked in LendingPool refer to reserve.reserveAssetAddress i.e crvUSD. When LendingPool use _depositIntoVault(), called in _rebalanceLiquidity which is used when user deposit(), withdraw() or borrow() some funds, it always refers to crvUSD value and do not account for the fact that scrvUSD is a rebasing token.

Meaning LendingPool will track crvUSD held by RToken (buffer) and staked crv in the vault. Those staked scrvUSD will accrue interest on the deposited amount into the curveVault, but do not grow into the accounting of LendingPool.

Note that _depositIntoVault() and _withdrawFromVault also have their own issue, but for the sake of explaining this one, let's consider that they both withdraw and deposit to the Curve Vault from the RToken contract, as it's supposed to keep crvUSD deposited.

LendingPool can only withdraw what has been deposited, it will then lead to all yield earnings being stuck in curveVault :

  1. liquidityBufferRatio = 20%

  2. user deposit 100 crvUSD

  3. 20 crvUSD will stay on RToken, and 80 will now be scrvUSD (with 10% yields)

  4. 1-year pass, 20 crvUSD still on RToken, but now 88 scrvUSD in the curveVault owned by RToken

  5. From LendingPool (which is also ReservePool in Rtoken) perspective, it only tracks reserve.totalLiquidity and users can only withdraw the equivalent of RToken they have.

  6. The user can only withdraw his 100 crvUSD against his 100 minted RToken, 8 scrvUSD will stay in the name of RToken in the curveVault

One could argue that the RToken index will follow the rebasing of scrvUSD, but its rebasing function updateLiquidityIndex is never called anywhere.

There is no function in LendingPool, nor RToken to withdraw accrued interest in the curveVault

Impact

All accrued interest in the curveVault will be locked forever in it. There is no way to get them back.

Tools Used

Manual

Recommendations

Add a function that only the owner or a special role can call, or that trigger on _updateLiquidity that withdraws excedent from the curveVault

//In LendingPool
address scrvUSD = 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367 //https://etherscan.io/address/0x0655977FEb2f289A4aB78af67BAB0d17aAb84367
function withdrawYieldsFromVault(uint256 amount) public onlyOwner {
uint256 totalWithYieldsInVault = IERC20(scrvUSD).balanceOf(reserve.reserveRTokenAddress);
uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
uint256 totalInVault = totalDeposits - currentBuffer
uint256 yields = totalWithYieldsInVault - totalInVault
rtoken.withdrawYields(yields)
}
//In RToken
function withdrawYields(uint256 amount) external onlyReservePool {
curveVault.withdraw(amount, treasury, address(this), 0, new address[](0));
}

The code is not 100% at whom should keep scrvUSD. Though all crvUSD are kept by RToken, so I've taken the assumption that it should also keep scrvToken

Updates

Lead Judging Commences

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

RToken::updateLiquidityIndex() has onlyReservePool modifier but LendingPool never calls it, causing transferFrom() to use stale liquidity index values

Support

FAQs

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