claimRedemption function in the Transmuter is designed to handle cases where the system can't obtain enough yield tokens (MYT) to fulfill a user's entire claim. It correctly caps the amount of MYT sent to the user. but the function fails to refund the corresponding portion of the user's synthetic asset (alAsset) that would be burned but not converted. resulting in a permanent loss of funds for the user, as their alAsset is destroyed without them receiving the equivalent value in yield tokens.
Vulnerability Details
the issue is because how the cap mechanism does not completely handle if the MYT in transmuter after redeem does not sufficient to cover user transmuted amount:
uint256 totalYield = alchemist.convertDebtTokensToYield(scaledTransmuted);// Cap to what we actually hold now (handles redeem() rounding shortfalls).uint256 balAfterRedeem = TokenUtils.safeBalanceOf(alchemist.myt(),address(this));@>uint256 distributable = totalYield <= balAfterRedeem ? totalYield : balAfterRedeem;uint256 syntheticFee = amountNottransmuted * exitFee / BPS;@>uint256 syntheticReturned = amountNottransmuted - syntheticFee;
in distributable , if the totalYield which user should get is greater than balAfterRedeem then it would cap the distributable into balAfterRedeem . but if we check the syntheticReturned there are no adjustment if the distributable is capped, making it always return the syntheticReturned amount which if converted to yield token it would be greater than distributable amount.
Impact Details
user would burn more alAsset compared to what MYT amount they received, making it user loss.
if transmuter would sent lower MYT amount than what user would burn, we should only burn the alAsset that equal to actual MYT that get sent. the cap should not only applied on the MYT side, but the user amount side.
to understand this better, lets list the preconditions:
Transmuter have some MYT on the contract (via user repay). this also reduce the cumulativeEarmark which is used to cap redeem amount on step 4.
after some time price of MYT dropped
user1 claimRedemption
now the cap happen because the Alchemsit::redeem would return fewer MYT (because of step 1 and 2 combined). this combined with what Transmuter held before redeem would not sufficient for what user1 supposed to get. for example user1 would get 100 MYT but contract only held 80 MYT, the 80 MYT would get sent to user1
user1 still burn alAsset equivalent of 100 MYT, where he get only 80 MYT. user1 loss 20 MYT worth of alAsset for nothing because of it
now add the test into src/test/AlchemistV3.t.sol:
run with forge test --mt test_excessAlAssetFromCappedRedeemDoesNotReturnedToUser
this prove that the mytOut is lower than what user alAsset burned converted to yield