58185 sc medium incorrect survivalaccumulator accounting logic after earmarkweight reaches 128 breaks core system invariants and can lead to protocol insolvency
Submitted on Oct 31st 2025 at 08:44:55 UTC by @luc1jan for Audit Comp | Alchemix V3
Report ID: #58185
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Description
Brief/Intro
After state variable _earmarkWeight that represents index of amounts earmarked globally reaches 128 in decimal or LOG2NEGFRAC_1, _survivalAccumulator starts to accrue inflated amounts that leads to accounts debt being inflated and severely undercollateralized.
Vulnerability Details
As users create and claim redemptions in Transmuter, _survivalAccumulator changes on each earmark and redemption. At some earmark event, delta _survivalAccumulator is product of previousSurvival and earmarkedFraction:
_survivalAccumulator += _mulQ128(previousSurvival, earmarkedFraction);Where: previousSurvival is product of all "survival" fractions of past earmark events. _earmarkWeight is just logarithmically transformed version of this value (e.g. 50% gets earmarked + another 50%; previousSurvival is 1/2 * 1/2 = 1/4) and earmarkFraction is portion of globally unearmarked debt that was earmarked in current earmark event. (e.g. 20 out of 100 gets earmarked; earmarkFraction is 0.2)
With this in mind general _survivalAccumulator equation is: ps1 * ef1 + ps2 * ef2 + ...
Note that ps_n is product of all previous survival fractions and first survival is 1 so we can write above as: 1 * ef1 + s1 * ef2 + s1*s2 * ef3 + s1*s2*s3 * ef4 + ...
Users on each sync update their earmarked debt based on change between their last stored lastSurvivalAccumulator and the global one. This is very convenient mathematically because that delta _survivalAccumulator on user sync will look something like this (e.g. 2 earmark events happened since last user sync, and there were 4 in total): s1*s2 * ef3 + s1*s2*s3 * ef4
Users have their previousSurvival stored as well in form of lastAccruedEarmarkWeight which is exactly the previousSurvival that can be factored out in this delta: s1*s2(ef3 + s3*ef4)
The sum inside brackets is exactly the fraction of users unearmarked debt that should be earmarked. The point here to understand is that previousSurvival is very important because it scales down the next fraction because new portion is portion of previously survived debt, not all time total.
However, when _earmarkWeight reaches 128 ("all of unearmarked debt has been earmarked"), previousSurvival becomes 0 ("nothing survived"). To safeguard against division by 0, it's set to 1:
This means that _mulQ128(previousSurvival, earmarkedFraction) product will always be just an earmarkedFraction, NOT scaled down by previousSurvival.
This will make the difference between users lastAccruedEarmarkWeight and previousSurvival look like this: 1*ef3 + 1*ef4
The problem is that 1*ef4 is not scaled down by previous survival and this will make users earmarked debt inflated which will lead to overall debt inflated. Note that the vulnerability does not require any malicious actors as system will break by itself once _earmarkWeight reaches 128 in decimal.
Impact Details
After _earmarkWeightreaches 128, new inflows of debt will be more and more inflated on each earmark event. This will cause core system invariant to break:
Sum of all users debt > Global debt
Additionally, users will become immediately undercollateralized and liquidatable because of account.debt inflation. This can cause several issues down the line as accounts debt becomes bigger than collateral which makes it look like bad debt accrued so every liquidation will take outsourced fees or none which will remove incentive for liquidators.
On each liquidation that goes through globalDebt will decrease more than it should because of inflated account.debt subtraction, which means global state will also be incorrect which breaks the whole protocol.
References
https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L1117-L1129
https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L1042-L1095
Proof of Concept
Proof of Concept
Was this helpful?