When liquidating a position, the contract deducts the gross seizure (debt burn + base fee) from the user’s collateral. It then sends only the net amount (gross − fee) to the transmuter and attempts to pay the fee to the liquidator. If the account’s collateral is fully drained by the gross deduction (or the leftover is less than the fee), the fee transfer never occurs. The result is a fee that is charged to the user but not paid to the liquidator and not sent to the transmuter, effectively being withheld in the contract.
Vulnerability Details
In _doLiquidation, the contract:
Deducts the full gross seizure from the account’s collateralBalance.
Transfers only the net amount (gross − fee) to the transmuter.
Pays the base fee to the liquidator only if the account’s remaining balance is still at least the fee.
amountLiquidated equals the gross seizure in yield units , so line 871 reduces the account by gross. https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L871
The net (gross − fee) is sent to the transmuter (https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L875).
If the gross deduction drains the account (or leaves a leftover smaller than the fee), the conditional on line 878 fails and the liquidator receives no fee, even though that fee has already been accounted for in the gross deduction from the user. https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L878
Impact Details
Liquidators perform valid liquidations but do not receive the fee when the account is liquidated and the fee will stay in the contract.
Paste the test in AlchemistV3.t.sol and run the test using forge test -vvvv --match-path src/test/AlchemistV3.t.sol --match-test testLiquidate_FeeNotPaid_When_GrossEqualsBalance