Core Contracts

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

Broken NFT Liquidation Mechanism Due to Missing Transfer Flow and Approvals

Summary

The NFTLiquidator contract implements a critical liquidation mechanism that is completely broken due to two major design flaws:

  1. The liquidateNFT() function can only be called by StabilityPool, but StabilityPool has no mechanism to call it

  2. The function assumes StabilityPool has approved NFTLiquidator to transfer NFTs, but there's no approval mechanism in place

This creates a situation where NFTs can never enter the liquidation auction system, effectively breaking a core protocol mechanism.

Vulnerability Details

Let's break down how the liquidation flow fails:

  1. First, let's look at NFTLiquidator's requirements:

function liquidateNFT(uint256 tokenId, uint256 debt) external {
// Requirement 1: Only StabilityPool can call this
if (msg.sender != stabilityPool) revert OnlyStabilityPool();
// Requirement 2: Assumes StabilityPool has approved NFTLiquidator
nftContract.transferFrom(msg.sender, address(this), tokenId);
tokenData[tokenId] = TokenData({
debt: debt,
auctionEndTime: block.timestamp + 3 days,
highestBid: 0,
highestBidder: address(0)
});
indexToken.mint(stabilityPool, debt);
}
  1. However, examining StabilityPool, we find:

    • No function to call liquidateNFT()

    • No approval mechanism for NFTLiquidator

    • No way to initiate the liquidation auction process

  2. This creates a deadlock where:

    • NFTs can only be liquidated by StabilityPool (msg.sender check)

    • StabilityPool needs to approve NFTLiquidator (for transferFrom)

    • StabilityPool has no functions to do either

    • Result: NFTs can never enter the auction system

  3. The transfer would fail in two ways:

// Scenario 1: Called by anyone else
nftLiquidator.liquidateNFT(tokenId, debt);
// Reverts: "OnlyStabilityPool"
// Scenario 2: Even if StabilityPool could call it
// transferFrom would fail due to no approval
// Reverts: "ERC721: caller is not token owner or approved"

Impact

The impact for this:

  1. Core liquidation mechanism is completely broken

  2. No way to liquidate NFTs through the intended auction system

  3. Affects all liquidations, not just edge cases

  4. No workaround available within current contract design

  5. Could lead to protocol insolvency as bad debt cannot be liquidated

Root Cause

The root cause is a critical design flaw in the liquidation architecture:

  1. NFTLiquidator expects StabilityPool to:

    • Call liquidateNFT()

    • Have approved NFT transfers

  2. But StabilityPool has neither:

    • No function to call NFTLiquidator

    • No approval mechanism

  3. This creates an impossible condition where liquidations can never occur

Tools Used

Manual review

Recommendations

The protocol needs to implement a complete liquidation flow. Two potential approaches:

  1. Add required functionality to StabilityPool:

function initiateLiquidationAuction(uint256 tokenId, uint256 debt) external onlyOwner {
// Approve NFTLiquidator
nftContract.approve(address(nftLiquidator), tokenId);
// Start liquidation
nftLiquidator.liquidateNFT(tokenId, debt);
}
  1. Redesign the liquidation flow:

  • Remove the StabilityPool requirement from NFTLiquidator

  • Allow LendingPool to directly initiate liquidations

  • Implement proper access controls and approval mechanisms

Updates

Lead Judging Commences

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

Liquidated RAACNFTs are sent to the StabilityPool by LendingPool::finalizeLiquidation where they get stuck

Support

FAQs

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

Give us feedback!