Core Contracts

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

Liquidation Grace Period Unfairly Expires During Protocol Pause

Summary

In the LendingPool contract, when the protocol is paused, users cannot close their liquidation positions, but the grace period timer continues to count down. This can result in users losing their opportunity to close their position if the grace period expires during the pause, leading to unfair liquidations.

Vulnerability Details

In LendingPool.sol:

function closeLiquidation() external nonReentrant whenNotPaused {
address userAddress = msg.sender;
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// Grace period check continues counting even during pause
if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}
// ... rest of code
}

Attack Scenario:

  1. Alice's position becomes liquidatable at T=0

  2. Grace period is 3 days (liquidationGracePeriod = 3 days)

  3. At T=1 day, protocol is paused for emergency for 3 days

  4. When protocol unpauses at T=4 days:

    • 4 days > liquidationStartTime + 3 days grace period

    • Grace period has expired

    • Alice never had a real chance to close her position during the 3-day pause

    • Position is now vulnerable to immediate liquidation

Impact

High severity. Users can unfairly lose their positions without having the full promised grace period to act:

  • Grace period expires while users are unable to take action

  • Users lose their rightful window to save their positions

  • Can lead to forced liquidations immediately after unpause

  • Breaks core protocol fairness assumptions about grace periods

Tools Used

Manual Review

Recommendations

Add pause time tracking and adjust grace period checks:

contract LendingPool {
struct PauseInfo {
uint256 totalPausedDuration;
uint256 lastPauseTimestamp;
}
PauseInfo public pauseInfo;
function _pause() internal override {
super._pause();
pauseInfo.lastPauseTimestamp = block.timestamp;
}
function _unpause() internal override {
pauseInfo.totalPausedDuration += block.timestamp - pauseInfo.lastPauseTimestamp;
super._unpause();
}
function closeLiquidation() external nonReentrant whenNotPaused {
address userAddress = msg.sender;
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
// Adjust grace period check by subtracting paused time
uint256 effectiveTime = block.timestamp - pauseInfo.totalPausedDuration;
if (effectiveTime > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}
// ... rest of function
}
}

This ensures each user gets their full grace period opportunity regardless of protocol pauses, maintaining fairness in the liquidation process.

Consider also making closeLiquidation() callable during pause as an additional safety measure:

- function closeLiquidation() external nonReentrant whenNotPaused {
+ function closeLiquidation() external nonReentrant {
// ... rest of function
}

This allows users to close their positions even during protocol pauses, providing additional protection against unfair liquidations.

Updates

Lead Judging Commences

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

Unfair Liquidation As Repayment / closeLiquidation Paused While Liquidations Enabled

inallhonesty Lead Judge 3 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.