In the withdrawNFT function, the contract checks whether removing an NFT would leave the user undercollateralized by comparing the remaining collateral value to a percentage of the user’s debt. However, the check uses a percentage multiplication on the debt value (via userDebt.percentMul(liquidationThreshold)) that inadvertently lowers the effective debt threshold. This miscalculation permits withdrawals that should be disallowed, allowing users to remove NFTs even when doing so would put them under the required collateralization ratio. As a result, users may extract NFT collateral in scenarios where they should remain overcollateralized, jeopardizing the protocol’s solvency and exposing the system to potential liquidation or denial-of-service risks.
Incorrect Collateral Validation in withdrawNFT
The function includes the following check to prevent withdrawals that would leave the user undercollateralized:
Intended Logic:
The idea is to ensure that after removing the NFT, the user’s remaining collateral (collateralValue minus the value of the NFT being withdrawn) remains at least 80% of the user’s debt.
Issue with Calculation:*
The expression userDebt.percentMul(liquidationThreshold) effectively scales down the user's debt. For example, if the user's debt is 950 and the liquidation threshold is 80% (expressed as 8000 in basis points, assuming a factor of 10,000), the calculation would yield a threshold value of approximately 760. This reduced threshold means that the contract permits the withdrawal if the remaining collateral is above 760—even if the actual risk level should be higher.
Example Scenario
User Collateral: The user holds 10 NFTs valued at 100 each, giving a total collateral of 1000.
NFT to Withdraw: Withdrawing one NFT worth 100 would leave the user with a remaining collateral of 900.
User Debt: Suppose the user's total debt is 950.
The correct approach would require the remaining collateral to be at least 80% of the debt (which would be 760 if computed correctly, but the intended calculation should prevent unsafe withdrawals). Due to the bug, the check compares 900 (remaining collateral) against a reduced threshold (approximately 760), thereby allowing the withdrawal even when it might render the user undercollateralized.
Undercollateralized Withdrawals:
Users can withdraw NFTs even when doing so causes their remaining collateral to fall below safe levels. This allows users to extract collateral improperly, undermining the intended risk management.
Protocol Insolvency Risk:
Permitting undercollateralized states can lead to a situation where users’ positions are not adequately secured. This increases the risk of defaults and insolvency of the reserve, as the remaining collateral would be insufficient to cover the outstanding debt.
Economic Exploitation:
Malicious users may exploit this flaw to withdraw valuable NFTs from the protocol, effectively reducing the overall collateral backing the debt. This can lead to cascading liquidations or force the protocol to absorb losses.
Denial of Service (DoS):
In the long run, if many users take advantage of this bug, it may lead to severe undercollateralization across the system. This could force emergency shutdowns or trigger liquidation mechanisms that prevent further withdrawals, effectively creating a DoS for users trying to access their funds.
Manual review
Correct the Collateral Check:
Adjust the withdrawal check in withdrawNFT so that it accurately reflects the collateral requirement. Instead of applying percentMul directly on userDebt, consider aligning the check with the calculation in the calculateHealthFactor function:
Alternatively, rework the logic to calculate a health factor and ensure it stays above 1 after the NFT is withdrawn.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.