58544 sc critical it is possible to underflow on sync making positions bricked forever
Submitted on Nov 3rd 2025 at 07:06:27 UTC by @farismaulana for Audit Comp | Alchemix V3
Report ID: #58544
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Permanent freezing of funds
Protocol insolvency
Description
Brief/Intro
account.rawLocked is recalculated every time the _sync function invoked. it is crucial for the value to be updated because it is used to calculate how much collateral need to be removed every _sync . however there are no mechanism to make the collateral to remove from this calculation to always cap to account.collateralBalance . given how a combination of poke, redeem and yield token price drop would cause the collateral to remove bigger than account.collateralBalance leading to underflow.
Vulnerability Details
the issue is there are no cap mechanism to prevent the collateralToRemove to be maxed at account.collateralBalance
function _sync(uint256 tokenId) internal {
Account storage account = _accounts[tokenId];
// Collateral to remove from redemptions and fees
@> uint256 collateralToRemove = PositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeight);
@> account.collateralBalance -= collateralToRemove;
...
// Update locked collateral
@> account.rawLocked = convertDebtTokensToYield(account.debt) * minimumCollateralization / FIXED_POINT_SCALAR;note that the account.rawLocked is recalculated at the end of _sync, this would cause an issue if the yield token price drop, making the account.debt would need more rawLocked amount than before.
but the above are not enough to be an issue. there are second parameter that affect how much collateralToRemove, which is _collateralWeight that is updated on redeem function.
the issue to inflate this collateralToRemove to be bigger than account.collateralBalance can be achieved by:
minimum two redemption position on transmuter
yield token price drop
claim matured redemption → increase system
_collateralWeightpoke a tokenId → using updated
_collateralWeightremove the collateral, recalculateaccount.rawLockedusing price drop of yield token, this would result in increased rawLocked amount needed.claim another matured redemption → increase system
_collateralWeightany
_syncinteraction would revert underflow because now the result ofPositionDecay.ScaleByWeightDelta(account.rawLocked, _collateralWeight - account.lastCollateralWeightwould be greater thanaccount.collateralBalance
Impact Details
there are multiple impact of this issue:
permanent account freezing
unable to recover bad debt via repay/liquidate (or any function that have
_sync)protocol insolvency, unliquidated bad debt drains value of the entire system. the debt remains and the collateral is frozen while cant be seized.
the positions that bricked cant be recovered even if the price of yield increase, making the issue permanent once happen
References
https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L1042-L1086
https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L634
Proof of Concept
Proof of Concept
add the test to src/test/AlchemistV3.t.sol:
run with forge test --mt test_underflowOnSync and the repay call would fail of underflow.
note: we can also simulate yield price became even higher than mint time (2x price increase) by uncomment the total supply mock lines but it would not help the position that is bricked as it would still underflow.
at the end of the test, when _sync is invoked inside repay, the value of collateralToRemove is 1.422e20 while the account.collateralBalance is left with 5e19 .
we can log the value by creating an emit event and emit it inside the _sync for further analysis:
the trace:
Was this helpful?