57476 sc high forcerepay fails to decrement global cumulativeearmarked
Submitted on Oct 26th 2025 at 14:45:14 UTC by @Paludo0x for Audit Comp | Alchemix V3
Report ID: #57476
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Description
Brief/Intro
During liquidations, AlchemistV3::_forceRepay() correctly reduces the account earmarked value account.earmarked but does not reduce the global earmarked cumulativeEarmarked.
Vulnerability Details
The buggy snippet is the following:
function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) {
....
// Repay debt from earmarked amount of debt first
uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
account.earmarked -= earmarkToRemove;
creditToYield = creditToYield > account.collateralBalance ? account.collateralBalance : creditToYield;
account.collateralBalance -= creditToYield;
uint256 protocolFeeTotal = creditToYield * protocolFee / BPS;
emit ForceRepay(accountId, amount, creditToYield, protocolFeeTotal);
if (account.collateralBalance > protocolFeeTotal) {
account.collateralBalance -= protocolFeeTotal;
// Transfer the protocol fee to the protocol fee receiver
TokenUtils.safeTransfer(myt, protocolFeeReceiver, protocolFeeTotal);
}
if (creditToYield > 0) {
// Transfer the repaid tokens from the account to the transmuter.
TokenUtils.safeTransfer(myt, address(transmuter), creditToYield);
}
return creditToYield;
}While in repay() call global earmarked is reduced:
This breaks the core invariant that the global earmark tracks the aggregate of user earmarks.
Because cumulativeEarmarked is used both as the denominator for redemption weighting and to compute liveUnearmarked = totalDebt - cumulativeEarmarked for future earmarks, the error propagates into worngly priced redemptions and persistent accounting drift.
These are the relevant snippet where cumulativeEarmarked is used:
Impact Details
These are the main impacts:
Users debts are reduced less than they should during redemptions
Protocol global accounting corruption: divergence between burned synthetics and aggregate debt reduction
Protocol risk over time: repeated liquidations via
_forceRepay()can degrade solvency
Given the above impacts the severity is CRITICAL.
Proof of Concept
Proof of Concept
The PoC forces calling of _forceRepay() during a liquidation and demonstrates that global cumulativeEarmarked doesn't decrease after a liquidation.
This is the log:
Was this helpful?