57916 sc critical repay removes earmark meant to be reducing debt while collateral is still reduced
Submitted on Oct 29th 2025 at 12:58:41 UTC by @JoeMama for Audit Comp | Alchemix V3
Report ID: #57916
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
When a user repays a portion of his debt, the repay function will remove the earmark, but keep the collateral position, when sync is called afterwards, it will wrongly remove some of the users collateral but not remove the debt because earmark is 0.
One issue is that
Repayis paying off the debt that would normally been removed by sync. ( up to the earmark amount )The second issue is that it makes the user lose a portion of its collateral while not reducing debt during the next sync.
function repay(uint256 amount, uint256 recipientTokenId) public returns (uint256) {
.... snip ...
account.earmarked -= earmarkToRemove;
}Vulnerability Details
The root cause of the issue is that during repay, the earmark from a token is removed by the repay amount, while the collateral position is still active, so now the debt will be decreased by 0 while the collateral ( for that earmark amount) will still be reduced by the line:
The debt removed will be 0, making the account.debt remain the same:
Secondly the debt ( up to earmark ) that was removed by repay, would have normally been synced and removed from the debt:
Impact Details
When the user repays a part of his debt, while having active earmark, he will lose a portion of his collateral and not remove the debt, because the repay function repays the debt that would have normally been freed by syncing the position, also it doesn't remove the position so it will still process the removal of collateral.
Link to Proof of Concept
https://gist.github.com/hexens-joe/35a987b5c1a85e3e957b3c972cd2369f
Proof of Concept
Proof of Concept
Token 1 has 1.2e18 collateral
Token 2 has 1.2e18 collateral
Token 1 mints 1e18 debt
Token 2 mints 1e18 debt
Token 1’s owner calls
createRedemptionwith 1e18 of the tokens received from minting.10_000 seconds pass and the claim is ready.
Token 2 calls
repaywith 0.5e18 underlying -> reduces debt to 0.5e18 while still having 1.2e18 collateral. ( However this also reduced his earmarked, which was 0.5e18 because of the token1’s owner redemption. ) (this is the root cause of the bug)Token 1’s owner calls
claim redemption.Now during
synctoken 2 it's collateral will be reduced by 0.17e18, and remove 0 debt. (this is the impact of the bug )Now during
synctoken 1 it's collateral will be reduced by 0.33e18, and remove 0.5e18 debt.
Now token2 is worth less than he put in the protocol ( 1.7e18 underlying ):
Now token1 is worth more than he put in the protocol ( 1.2 underlying ):
POC:
You can add it to the existing test AlchemistV3Test, or run as shown in the gist.
output:
Was this helpful?