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:
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
Bob deposits 1500 myt to alchemist and mints 1000 alUSD
Bob transfers 501 alUSD to redeemer1 and 499 alUSD to redeemer 2
Both redeemers create redemptions with the amounts they got
warp time to when both redemtions are mature
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.
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.
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)
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)
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.
Copy the code below into the IntegrationTest contract is tes/IntergrationTest.t.sol
Run with FOUNDRY_PROFILE=default forge test --fork-url https://mainnet.gateway.tenderly.co --match-test testDoubleAccountingPOC -vvv --evm-version cancun