Summary
CrvUSD cannot be deposited into Curve USD Vault as there is no funds in LendingPool.
Vulnerability Details
_depositIntoVault() is used to deposit liquidity into the Curve vault.
LendingPool::_depositIntoVault():
function _depositIntoVault(uint256 amount) internal {
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
The function is called when rebalances liquidity between the buffer and the Curve vault, and the deposited amount is based on CrvUSD liquidity in RToken.
LendingPool::_rebalanceLiquidity():
uint256 totalDeposits = reserve.totalLiquidity;
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
@> _depositIntoVault(excess);
} else if (currentBuffer < desiredBuffer) {
Curve vault transfers deposit tokens from the caller to the vault, in our case, the tokens are transferred from LendingPool to Curve vault.
Yearn V3 Vault::_deposit():
self._erc20_safe_transfer_from(self.asset, msg.sender, self, assets)
The problem is that LendingPool has no CrvUSD, as the CrvUSD tokens have been transferred to RToken when user deposits.
ReserveLibrary::deposit():
IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender,
reserve.reserveRTokenAddress,
amount
);
Impact
CrvUSD cannot be deposited into Curve Vault and _depositIntoVault() will revert, this will blocks liquidity rebalance which is called when user deposits, withdraws and borrows.
POC
Please run forge test --mt testAudit_CannotDepositIntoVault:
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import "../contracts/core/pools/LendingPool/LendingPool.sol";
import "../contracts/core/tokens/RToken.sol";
import "../contracts/core/tokens/DebtToken.sol";
import "../contracts/core/tokens/RAACNFT.sol";
import "../contracts/core/primitives/RAACHousePrices.sol";
contract Audit is Test {
address owner = makeAddr("Owner");
address curveUSDVault;
address crvUSD;
LendingPool lendingPool;
RAACHousePrices raacHousePrices;
RToken rToken;
DebtToken debtToken;
RAACNFT raacNft;
function setUp() public {
vm.createSelectFork("https://eth.llamarpc.com");
curveUSDVault = 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367;
crvUSD = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E;
raacHousePrices = new RAACHousePrices(owner);
rToken = new RToken("RToken", "RToken", owner, address(crvUSD));
debtToken = new DebtToken("DebtToken", "DT", owner);
raacNft = new RAACNFT(address(crvUSD), address(raacHousePrices), owner);
lendingPool = new LendingPool(
address(crvUSD), address(rToken), address(debtToken), address(raacNft), address(raacHousePrices), 0.1e27
);
lendingPool.transferOwnership(owner);
vm.startPrank(owner);
raacHousePrices.setOracle(owner);
raacHousePrices.setHousePrice(1, 100);
rToken.setReservePool(address(lendingPool));
vm.stopPrank();
vm.label(curveUSDVault, "Curve USD Vault");
vm.label(crvUSD, "crvUSD");
vm.label(address(rToken), "RToken");
vm.label(address(debtToken), "DebtToken");
vm.label(address(raacNft), "RAAC NFT");
vm.label(address(lendingPool), "LendingPool");
}
function testAudit_CannotDepositIntoVault() public {
vm.prank(owner);
lendingPool.setCurveVault(curveUSDVault);
address bob = makeAddr("Bob");
deal(crvUSD, bob, 100e18);
vm.startPrank(bob);
IERC20(crvUSD).approve(address(lendingPool), 100e18);
vm.expectRevert();
lendingPool.deposit(100e18);
vm.stopPrank();
}
}
Tools Used
Manual Review
Recommendations
It is recommended to deposit into Curve Vault from RToken.