56965 sc critical alchemistv3 handling of added transmuter coverage includes an error that enables an attacker to cause protocol insolvency

Submitted on Oct 22nd 2025 at 09:10:50 UTC by @niroh for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #56965

  • Report Type: Smart Contract

  • Report severity: Critical

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol

  • Impacts:

    • Protocol insolvency

Description

Brief/Intro

AlchemistV3::redeem() removes from debt/earmark any coverage added to the Transmuter since the last redeem, however this is already handled withing repay().

Vulnerability Details

In AlchemistV3::redeem() the code attempts to remove from its debt/earmark any added coverage on the transmuter (capped by live Earmark), as can be seen here:

//From AlchemistV3.sol redeem function

    // observed transmuter pre-balance -> potential cover
    uint256 transmuterBal = TokenUtils.safeBalanceOf(myt, address(transmuter));
    uint256 deltaYield    = transmuterBal > lastTransmuterTokenBalance ? transmuterBal - lastTransmuterTokenBalance : 0;
    uint256 coverDebt = convertYieldTokensToDebt(deltaYield);

    console.log("in redeem yield delta is %s",deltaYield);

    // cap cover so we never consume beyond remaining earmarked
    uint256 coverToApplyDebt = amount + coverDebt > liveEarmarked ? (liveEarmarked - amount) : coverDebt;

    uint256 redeemedDebtTotal = amount + coverToApplyDebt;

However, when coverage is added to the Transmuter (through the repay() function) debt and earmark are already removed from the total debt/earmark (and from the repaying CDP):

While this scenario does not allways happen (due to certain conditions that must be met), an attacker can generate these conditions deliberately and cause this double accounting to reduce global debt/earmark (applied pro rata to all CDPs similar to redemptions) without a matching burn of synthetic tokens. The result is a general protocol insolvancy, where the circulating amount of synthetic tokens is not covered by collateral and can not be redeemed. The POC below demonstrates such a scenario.

POC scenario walkthrough

  1. Bob deposits 1500 myt to alchemist and mints 1000 alUSD

  2. Bob transfers 501 alUSD to redeemer1 and 499 alUSD to redeemer 2

  3. Both redeemers create redemptions with the amounts they got

  4. warp time to when both redemtions are mature

  5. bob sees a redeemer1 claimRedemption transaction in the mempool and frontruns it with repaying collateral worth 500 alUSD (this creates added coverage in the Transmuter, but not enough to cover the full redeemer1 redemption, which is key for this exploit). The collateral is added to Transmuter and the Alchemist debt/earmark is decreased by 500.

  6. redeemer1 claimRedemption transaction is executed. Most of the 501 tokens claimed is taken from the Transmuter coverage, but 1 token needs to be redeemed from AlchemistV3 and so AlchemistV3 redeem is called.

  7. AlchemistV3 redeem() redeems the 1 extra token, but also detects that coverage has increased by 500 and so tries to redeem 501 (capped to the available earmark which is 500 at that point)

  8. As a result, the entire debt and earmark in the system is cleared. Since bob has the only CDP, he can remove his entire remaining collateral (inspite of the fact that 499 alUSD is still in circulation and is matured for redemption)

  9. If redeemer 1 tries to claim their redemption now, the transaction will succeed but they will get 0 because there is no debt/earmark left in the system nor coverage on the transmuter.

Impact Details

Protocol insolvancy through removing debt (and thus freeing collateral) that is required to cover the current issuance of synthetic tokens, leaving the protocol without ability to support redemptions not maintain the peg.

References

https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L601

Proof of Concept

Proof of Concept

To run:

  1. Copy the code below into the IntegrationTest contract is tes/IntergrationTest.t.sol

  2. Run with FOUNDRY_PROFILE=default forge test --fork-url https://mainnet.gateway.tenderly.co --match-test testDoubleAccountingPOC -vvv --evm-version cancun

Was this helpful?