The _subDebt() function in AlchemistV3 calculates how much collateral can be freed (toFree) when the debt (debt) is reduced:
function_subDebt(uint256tokenId,uint256amount)internal{ Account storage account = _accounts[tokenId];// collateral that can be released from reducing the debt `amount`uint256 toFree =convertDebtTokensToYield(amount)* minimumCollateralization / FIXED_POINT_SCALAR;// collateral that is locked before reductionuint256 lockedCollateral =convertDebtTokensToYield(account.debt)* minimumCollateralization / FIXED_POINT_SCALAR;if(toFree > _totalLocked){ toFree = _totalLocked;// clamp (global) 🔹} account.debt -= amount; totalDebt -= amount; _totalLocked -= toFree;// per-account lock update account.rawLocked = lockedCollateral - toFree;// underflow}
Free and lock Collateral both depend on the conversion chain:
Since all operations perform rounding down (floor), the results for large numbers (account.debt) and small numbers (amount) are not guaranteed to be proportional. At a certain threshold, toFree can exceed the smallest unit of lockedCollateral (±1 wei) resulting in a reduction:
becomes negative and triggers panic(0x11) (arithmetic underflow), the existing Clamp only compares toFree with _totalLocked (global), not with lockedCollateral (per-account) and so does not prevent individual underflows.
Impact
Transactions called repay, burn, liquidate, or other internal functions that call _subDebt() may revert on edge-case conditions. Users cannot repay or liquidate positions even if they are economically sound, resulting in funds being frozen until parameters (APR and price) change.
Recommendation
Either clamp toFree against lockedCollateral (per-account) before the deduction, or recalculate rawLocked using the debt balance after the deduction.