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 V3arrow-up-right

  • 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 Repay is 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.

https://gist.github.com/hexens-joe/35a987b5c1a85e3e957b3c972cd2369f

Proof of Concept

Proof of Concept

  1. Token 1 has 1.2e18 collateral

  2. Token 2 has 1.2e18 collateral

  3. Token 1 mints 1e18 debt

  4. Token 2 mints 1e18 debt

  5. Token 1’s owner calls createRedemption with 1e18 of the tokens received from minting.

  6. 10_000 seconds pass and the claim is ready.

  7. Token 2 calls repay with 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)

  8. Token 1’s owner calls claim redemption.

  9. Now during sync token 2 it's collateral will be reduced by 0.17e18, and remove 0 debt. (this is the impact of the bug )

  10. Now during sync token 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?