Core Contracts

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

Users of LendingPool can get liquidated while the contract is paused

Summary

Users could get liquidated while the LendingPool contract is paused, and by this prevent the user from repaing the debt and closing the liqudation before the grace period ends.

Vulnerability Details

Due to the reason that the LendingPool has not enforced the whenNotPaused modifier for the finalizeLiquidation a user could get liquidated without having the chance to repay his/her debt and close the liquidation before the end of the grace period.

The reason for this is because both the repay and closeLiquidation functions requiere the contract not to be paused in order to be accessed while the finalizeLiquidation function does not. This opens up a scenario when an users health factor goes bellow the liquidation threshold and somone calls initiateLiquidation for that user. If the LendingPool contract gets paused before the user is able to reapay his/her debt and close the liquidation we might end up in a scenario when the pause period of the contract is longer than the grace period for the liquidation and after the grace period has passed the Stability Pool can call finalizeLiquidation for that user and liquidate him/her even while the contract is still paused.

Impact

  • User could get unfairly liquidated without having the chance to repay debt and improve collaterall health.

  • Finalization of liquidations is possible to be called while the contract is in a paused state.

Tools Used

  • Manual Review

  • Unit test

PoC

  • Copy this test and put it in the LendingPool.test.js file inside of the <describe("Liquidation")> test suite

it("POC User can be liquidated while contract is paused", async function () {
// Decrease house price and initiate liquidation for user2
console.log("Contract is paused: ", await lendingPool.paused());
await raacHousePrices.setHousePrice(1, ethers.parseEther("90"));
console.log("User 1 health factor is: ", await lendingPool.calculateHealthFactor(user1.address));
await lendingPool.connect(user2).initiateLiquidation(user1.address);
console.log("Pausing contract...")
// Lending Pool contract gets paused for some reason
lendingPool.connect(owner).pause();
// User2 tries to repay the debt but fails because the contract is paused
const userDebt = await lendingPool.getUserDebt(user1.address);
console.log("Contract is paused: ", await lendingPool.paused());
console.log("============================================")
console.log("User 1 debt is: ", userDebt);
await crvusd.connect(user1).approve(lendingPool.target, userDebt + ethers.parseEther("1"));
await expect(lendingPool.connect(user1).repay(userDebt + ethers.parseEther("1"))).to.be.revertedWithCustomError(lendingPool, "EnforcedPause");
console.log("Simulating 72hours have passed(this is the grace period)....")
// Advance time beyond grace period (72 hours)
await ethers.provider.send("evm_increaseTime", [72 * 60 * 60 + 1]);
await ethers.provider.send("evm_mine");
// Fund the stability pool with crvUSD
await crvusd.connect(owner).mint(owner.address, ethers.parseEther("1000"));
// Set Stability Pool address (using owner for this test)
await lendingPool.connect(owner).setStabilityPool(owner.address);
console.log("Finilizing liquidation of user....")
//Finilize user2 liquidation while the contract is still paused
await expect(lendingPool.connect(owner).finalizeLiquidation(user1.address))
.to.emit(lendingPool, "LiquidationFinalized")
console.log("==================================================")
// Verify that the user is no longer under liquidation
expect(await lendingPool.isUnderLiquidation(user1.address)).to.be.false;
console.log("Stability Pool address is: ", await lendingPool.stabilityPool());
console.log("User 1 address is: ", user1.address);
console.log("Owner of NFT 1 is :", await raacNFT.ownerOf(1));
// Verify that the NFT has been transferred to the Stability Pool
expect(await raacNFT.ownerOf(1)).to.equal(owner.address);
// Verify that the user's debt has been repaid
const userClosedLiquidationDebt = await lendingPool.getUserDebt(user1.address);
console.log("User 1 debt after liquidation is: ", userClosedLiquidationDebt);
expect(userClosedLiquidationDebt).to.equal(0);
// Verify that the user's health factor is now at its maximum (type(uint256).max)
const healthFactor = await lendingPool.calculateHealthFactor(user1.address);
console.log("User 1 health factor after liquidation is: ", healthFactor);
console.log("Contract is paused: ", await lendingPool.paused());
expect(healthFactor).to.equal(ethers.MaxUint256);
});

Console log output:

Contract is paused: false
User 1 health factor is: 900000000000000000n
Pausing contract...
Contract is paused: true
============================================
User 1 debt is: 80000000000000000000n
Simulating 72hours have passed(this is the grace period)....
Finilizing liquidation of user....
User balance is: 80018906416248234150
The amount of debt tokens to burn is: 80018906416248234150
==================================================
Stability Pool address is: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
User 1 address is: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Owner of NFT 1 is : 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
User 1 debt after liquidation is: 0n
User 1 health factor after liquidation is: 115792089237316195423570985008687907853269984665640564039457584007913129639935n
Contract is paused: true

Recommendations

Enable user either to:

  • Repay debt while the contract is paused and close liquidation.

  • Deposit more collateral and enable partial liquidation to bring back position to a healthy state.

  • Pause liquidations while the contract is paused and introduce a grace period for repaymet after the contract is unpaused.

Updates

Lead Judging Commences

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

Unfair Liquidation As Repayment / closeLiquidation Paused While Liquidations Enabled

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

Unfair Liquidation As Repayment / closeLiquidation Paused While Liquidations Enabled

Support

FAQs

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

Give us feedback!