58518 sc critical liquidation will steal repayment fee from innocent users funds

Submitted on Nov 2nd 2025 at 23:47:30 UTC by @gizzy for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58518

  • Report Type: Smart Contract

  • Report severity: Critical

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol

  • Impacts:

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Summary

The _resolveRepaymentFee() function in AlchemistV3.sol has a critical accounting bug where it returns the calculated fee amount instead of the actual amount deducted from the user's collateral. When a liquidated account has insufficient collateral to cover the full repayment fee, the function caps the deduction to the available balance but still returns the uncapped fee. This causes the protocol to transfer more MYT shares than it actually deducted from the account, stealing funds directly from other users' collateral.

Vulnerability Details

Root Cause

In AlchemistV3.sol:909-916, the _resolveRepaymentFee() function:

function _resolveRepaymentFee(uint256 accountId, uint256 repaidAmountInYield) internal returns (uint256 fee) {
    Account storage account = _accounts[accountId];
    // calculate repayment fee and deduct from account
    fee = repaidAmountInYield * repaymentFee / BPS;
    account.collateralBalance -= fee > account.collateralBalance ? account.collateralBalance : fee;
    emit RepaymentFee(accountId, repaidAmountInYield, msg.sender, fee);
    return fee;  // ← BUG: Returns calculated fee, not actual deduction
}

The Bug:

  1. Line 912: Calculates fee = repaidAmount × 3%

  2. Line 913: Deducts min(fee, account.collateralBalance) from the account

  3. Line 915: Returns the original fee, not the capped amount

The returned fee is sent to the liquidator via TokenUtils.safeTransfer(myt, msg.sender, feeInYield) in two places:

Location 1: _liquidate() line 833:

Location 2: _liquidate() line 847:

Step-by-Step Attack Vector

Initial Setup

  • Alice: Deposits 1,000 MYT shares worth $1,000, borrows $900 (max LTV)

  • Bob: Deposits 1,000 MYT shares worth $1,000, borrows nothing (provides liquidity)

  • Total Alchemist Balance: 2,000 MYT shares

Attack Execution

Step 1: Alice Creates Redemption

  • Alice creates redemption in Transmuter with all 900 alTokens

  • Time passes: Full transmutation period

  • Earmarked debt: ~$899

Step 2: Price Crash

  • MYT price drops 10%

  • Alice's collateral value: 1,000 shares × $0.909 = $909

  • Alice's debt: $900

  • Collateralization: 101% (below 105% threshold)

Step 3: Liquidation

Liquidator calls liquidate(aliceTokenId):

Bob Tries to Withdraw

When Bob tries to withdraw his full 1,000 shares:

Bob is now DOS'd from his own funds or will receive less than his deposited amount.

Impact

Direct Theft of User Funds

  • Innocent users lose funds to cover fees from insolvent accounts

DOS

  • When multiple users with insufficient collateral are liquidated, the theft compounds

  • Eventually, the Alchemist contract becomes insolvent (_mytSharesDeposited > actual balance)

Likelihood

High - This occurs whenever:

  • A liquidated account has insufficient collateral to cover the full 3% repayment fee

Reference

https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L900C3-L907C6

Proof of Concept

Proof of Concept

Add this test to src/test/AlchemistV3.t.sol (https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/test/AlchemistV3.t.sol):

Running the POC

Was this helpful?