56435 sc critical alchemistv3 repayment only liquidation pays liquidator from pool fee leak theft of unclaimed yield
Submitted on Oct 15th 2025 at 23:06:08 UTC by @humaira45 for Audit Comp | Alchemix V3
Report ID: #56435
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Theft of unclaimed yield
Description
Brief/Intro
In AlchemistV3’s liquidation pipeline, the “repayment‑only” branch (i.e., when earmarked debt fully clears a borrower’s debt or restores their ratio above the lower bound) pays a “repayment fee” to the liquidator. Due to a subtle accounting bug, the fee is not limited to what can actually be deducted from the borrower’s collateral. Instead, the contract transfers the full fee target from its own MYT balance (the shared pool), even if the borrower had less collateral than the fee.
In practice, this leaks MYT from the protocol pool to the liquidator. Our Foundry PoC shows a clean 5% leak per repayment‑only liquidation when minimumCollateralization = 110% and repaymentFee = 15%: the borrower can only fund 10% from collateral, yet the liquidator always receives 15%. We also top‑up the pool via a second depositor (not by minting straight to the contract) to remove any argument about artificial balance; the leak still drains pool liquidity and prevents other depositors from withdrawing their credited collateral.
Vulnerability Details
What the contract does
Earmark and repayment-first liquidation: When a position is liquidatable, AlchemistV3 first repays earmarked debt via _forceRepay(), then:
If debt is fully cleared or the account becomes healthy, the “repayment‑only” branch returns early (no seize), paying a “repayment fee” to the liquidator.
Otherwise, it proceeds to seize collateral in _doLiquidation().
Where the leak happens
Why this leaks funds
When minimumCollateralization is 110%, a full earmark repay of debt D leaves borrower collateral ≈ 10% × D.
With repaymentFee set to 15%, feeTarget is 15% × D. The borrower can only fund 10% × D; the 5% difference comes from the contract’s MYT balance (the shared pool), not the borrower.
This is theft of unclaimed yield: liquidators are overpaid beyond what the borrower’s collateral actually covers.
Root cause (code level)
Impact Details
Severity: High — Theft of unclaimed yield (explicitly in scope)
Liquidators are paid more than the borrower can cover from their collateral; the delta is taken from the shared MYT pool.
This can be farmed across many small accounts when earmarks are present (repayment‑only branch), cumulatively draining pool liquidity.
References
In‑scope contract: AlchemistV3.sol
Functions: _liquidate(), _resolveRepaymentFee()
Related interfaces used in PoC:
IAlchemistV3Position (position NFT)
MockTransmuter (queryGraph drives earmark → repayment‑only path)
Link to Proof of Concept
https://gist.github.com/humairar301-droid/d5788d36cc5c5efeb36253c69a879f83
Proof of Concept
Proof of Concept
What this PoC proves (end‑to‑end)
Sets up AlchemistV3 via proxy (ERC1967Proxy) and realistic parameters (minCollat 110%, repaymentFee 15%, protocolFee 0).
Borrower deposits 110 MYT and mints 100 debt; second depositor (whale) deposits 5 MYT via deposit() to ensure the pool has enough to pay a 15 MYT fee.
Next block: liquidation triggers repayment‑only (full earmark repay). We assert: Event ForceRepay(..., creditToYield=100e18). Event RepaymentFee(..., fee=15e18). transmuter receives 100e18; liquidator receives 15e18 from the contract. Pool outflow = 115e18. Borrower’s fee actually deducted from collateral is only 10e18 (clamped).
The 5e18 difference is a leak from the pool (theft of unclaimed yield).
The whale depositor then tries to withdraw 5e18; it reverts because the pool has been drained.
How to run
Full PoC (single file) Link Gist: https://gist.github.com/humairar301-droid/d5788d36cc5c5efeb36253c69a879f83
Save the test as: src/test/AlchemistV3_RepaymentFeeLeak.t.sol Run:
forge test --match-test test_RepaymentFeeLeak_WithWhaleDeposit_MismatchAndWithdrawFail -vvvv --evm-version cancun
Representative results:
Why this is in‑scope and feasible
In‑scope asset: AlchemistV3.sol (core protocol logic).
Impact explicitly listed: “Theft of unclaimed yield” (High).
No exotic assumptions:
We drive the normal liquidation path: earmark → _forceRepay → repayment‑only branch.
The pool top‑up comes from a second user via the real deposit() flow (not by minting to the contract), so the leak demonstrably drains real depositor liquidity.
Replicable: The attack can be repeated across many accounts with earmarks to cumulatively drain the pool.
Suggested remediation
The repayment‑only liquidation path overpays liquidators by charging the protocol pool, not just the borrower. Our PoC shows the mismatch on‑chain (event + transfer deltas) and demonstrates the immediate impact on another depositor. This is a clear High severity theft of unclaimed yield. The suggested one‑line fix (return feePaid, not feeTarget) resolves the issue without breaking external interfaces.
Was this helpful?