Summary
The LendingPool contract can not deposit into Curve Vault in the process of rebalancing liquidity because of insufficient balance
Vulnerability Details
At the end of the functions LendingPool#deposit()
, LendingPool#withdraw()
,LendingPool#borrow()
, the internal funtion LendingPool#_rebalanceLiquidity()
is called to rebalance liquidity between buffer and CurveVault to maintain the desired buffer ratio.
function _rebalanceLiquidity() internal {
if (address(curveVault) == address(0)) {
return;
}
@> 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) {
uint256 shortage = desiredBuffer - currentBuffer;
_withdrawFromVault(shortage);
}
emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);
}
function _depositIntoVault(uint256 amount) internal {
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
As reserve asset tokens are held by reserve.reserveRTokenAddress
address, then the logic inside the function LendingPool#_depositIntoVault()
will be failed because of insufficient token balance.
PoC
Add this MockCurveVault to the testsuite
pragma solidity ^0.8.19;
import "../primitives/MockContractWithConfig.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MockCurveVault is MockContractWithConfig {
IERC20 token;
function setToken(address _token) public {
token = IERC20(_token);
}
function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
token.transferFrom(msg.sender, address(this), assets);
}
function withdraw(
uint256 assets,
address receiver,
address owner,
uint256 maxLoss,
address[] calldata strategies
) external returns (uint256 shares) {
token.transfer(msg.sender, assets);
}
}
Add the test below to test/unit/core/pools/LendingPool/LendingPool.test.js
describe("Deposit and Withdraw", function () {
it.only("rebalance liquidity failed", async function () {
const depositAmount = ethers.parseEther("100");
const curveVaultFactory = await ethers.getContractFactory("MockCurveVault");
const curveVault = await curveVaultFactory.deploy();
await curveVault.setToken(await crvusd.getAddress());
await lendingPool.connect(owner).setCurveVault(await curveVault.getAddress());
await lendingPool.connect(user1).deposit(depositAmount);
});
...
Run the test and it failed
1) LendingPool
Deposit and Withdraw
rebalance liquidity failed:
Error: VM Exception while processing transaction: reverted with custom error 'ERC20InsufficientBalance("0x0165878A594ca255338adfa4d48449f69242Eb8F", 0, 160000000000000000000)'
at crvUSDToken.burnFrom (contracts/mocks/core/tokens/crvUSDToken.sol:37)
at crvUSDToken._transfer (@openzeppelin/contracts/token/ERC20/ERC20.sol:178)
at crvUSDToken.transferFrom (@openzeppelin/contracts/token/ERC20/ERC20.sol:157)
at MockCurveVault.deposit (contracts/mocks/core/pools/MockCurveVault.sol:18)
at LendingPool._depositIntoVault (contracts/core/pools/LendingPool/LendingPool.sol:827)
at LendingPool._rebalanceLiquidity (contracts/core/pools/LendingPool/LendingPool.sol:808)
at LendingPool.deposit (contracts/core/pools/LendingPool/LendingPool.sol:236)
...
Impact
Tools Used
Manual
Recommendations
Retrieve funds from reserveRTokenAddress
address before depositing into Curve vault