Blockchain security isn't optional.

Protect your smart contracts and DeFi protocols with Three Sigma, a trusted security partner in blockchain audits, smart contract vulnerability assessments, and Web3 security.

Get a Quote Today

Introduction to MIM Spell (Abracadabra.money)

Abracadabra.money is a platform that lets users deposit interest‐bearing tokens (ibTKNs) as collateral to borrow Magic Internet Money (MIM), a USD‐pegged stablecoin. By allowing these ibTKNs (e.g., yVault tokens) to serve as collateral, users can unlock additional capital previously locked in yield‐bearing positions.

Under the hood, Abracadabra is composed of modular smart contracts on top of BentoBox technology. This architecture provides essential features such as non‐custodial vaulting (via the DegenBox), isolated lending markets (the Cauldrons), and yield strategies that earn interest on stored collateral. When users deposit collateral in a specific Cauldron, they can borrow MIM against it. If their collateral value drops below a certain threshold, liquidators can repay the user’s debt in exchange for seizing collateral.

Architecture Overview

image
  1. DegenBox (BentoBox Variant)
    • Core vault that custody user funds and converts them into “shares.”
    • These shares can earn interest via deployed strategies (e.g., farming yield).
    • Abracadabra stores both user collateral and unborrowed MIM within the DegenBox.
  2. Cauldrons (Isolated Markets)
    • Each Cauldron is a “mini‐Kashi” market dedicated to a specific collateral type.
    • Handles the logic for borrowing MIM, tracking solvency, and performing liquidations.
    • Users interact with the Cauldron to deposit collateral and request MIM loans.
  3. Oracles
    • Each Cauldron must know the USD value of its collateral in real time.
    • Typically uses Chainlink or similar external oracles, or a “Proxy Oracle” that can be updated.
  4. Strategies
    • Optional modules connected to the DegenBox to deploy idle collateral into yield farms.
    • Increases the overall value of shares for all depositors in that token.
  5. Periphery Contracts
    • Swappers: Atomic “actions” that swap tokens to enable leverage or repay debt.
    • Wrappers: Adapters for special collaterals that have extra functionalities or reward tokens.
    • Withdrawers: Collect and distribute protocol fees.
    • CauldronOwner: A master contract owner controlling parameters across all Cauldrons.

Together, these contracts allow Abracadabra to create multiple isolated lending markets, each with customized parameters, while ensuring the MIM stablecoin remains backed by collateral at all times.

GMX V2 CauldronV4 Architecture

image

To support GMX V2 (an on‐chain perpetual DEX), Abracadabra introduced a specialized GmxV2 CauldronV4. GMX V2 has non‐atomic deposits and withdrawals: a user places an order, and a keeper or external callback eventually fulfills it. Below are the key adaptations that let Abracadabra handle this asynchronous flow:

  1. OrderAgent & RouterOrder
    • When users want to deposit GMX positions as collateral (or withdraw them), the Cauldron calls an OrderAgent.
    • This OrderAgent deploys a RouterOrder proxy specifically tied to that user.
    • The RouterOrder submits the deposit/withdraw request to GMX. Once the request completes, GMX calls back into afterDepositExecution() or afterWithdrawalCancellation().
  2. Pending Collateral in the Router
    • Normally, Cauldron checks a user’s solvency using only on‐chain collateral.
    • With GMX V2, the user might have a deposit request “in flight,” still not executed.
    • GmxV2 CauldronV4 extends _isSolvent() to count the user’s “pending” collateral in the RouterOrder. The function orderValueInCollateral() in the RouterOrder contract returns how many tokens the user effectively has “in transit.”
  3. Cook Actions
    • Abracadabra’s cook() function can bundle actions like depositing collateral, borrowing MIM, swapping, etc.
    • GmxV2 CauldronV4 adds specialized actions (ACTION_CREATE_ORDER, ACTION_WITHDRAW_FROM_ORDER, ACTION_CANCEL_ORDER) to manage GMX deposit/withdraw orders.
    • This design allows advanced, single‐transaction flows (e.g., “borrow then open a GMX deposit order”).
  4. Liquidation & closeOrder
    • In normal Cauldrons, if a user’s collateral is too low, the protocol seizes on‐chain tokens.
    • GmxV2 CauldronV4 must also account for any active order. It will cancelOrder() if the user is insolvent, and forcibly pull USDC or GMX tokens from the RouterOrder.
    • The function closeOrder() sets the user’s stored RouterOrder address to zero, finalizing the forcibly closed position.

Flow Example:

  1. Deposit: A user “creates an order” for depositing USDC in GMX. The deposit eventually yields GM tokens, which the Router sends back to the Cauldron as collateral.
  2. Borrow: If the user is still “solvent” considering both on‐chain collateral and pending “orderValueInCollateral(),” the Cauldron allows them to borrow MIM.
  3. Withdrawal: The user can similarly withdraw collateral via another GMX order, and once GMX processes it, tokens are returned or canceled.
  4. Liquidation: If user’s effective collateral drops below the threshold, the Cauldron calls liquidate(), potentially canceling any unfilled order (returning leftover USDC) or seizing GM tokens from a deposit that just arrived.

In Practice, this approach bridges the asynchronous design of GMX V2 with Abracadabra’s standard Cauldron architecture. However, as seen in the exploit, if the system does not carefully update the “pending collateral” after partial liquidations or withdrawals, an attacker can appear solvent on paper while draining the protocol in reality.

Audit Coverage

The gmCauldronV2 contract was reviewed by Guardian Audits in a report dated November 14, 2023 (audit PDF). The audit identified a significant number of issues, including 4 Critical/High severity findings and 10 Medium severity findings — a signal that the codebase required further refinement.

Importantly, this was the only audit conducted before the contracts were deployed. While Guardian completed their scope professionally, no follow-up audit was performed after substantial architectural changes

With that many vulnerabilities uncovered, a second audit isn't just recommended - it’s absolutely essential.

The exploit that followed was not caused by an audit oversight but rather by the protocol team's failure to seek deeper validation after integration changes.

Attack Analysis

Context: GmxV2 CauldronV4’s Non‐Atomic Deposits

Abracadabra’s GmxV2 CauldronV4 was designed to handle GMX V2's non-atomic deposit model, where users create deposit orders that are executed later. When a deposit succeeds, GM tokens are credited to the user's collateral. However, when a deposit fails—for example, due to an unreachable minOut—the GMX contract returns the original tokens (e.g., USDC) to the RouterOrder. In such cases, the Cauldron still considers these returned tokens as valid collateral.

The attacker leveraged this by intentionally forcing deposit failures, creating "phantom collateral" in the RouterOrder contract. The orderValueInCollateral() function continued to report the full value of the failed deposit, and the Cauldron did not mark the order as closed even after extracting real funds from the RouterOrder.

Preparation and Attack Timeline

The following sequence outlines the attacker’s setup before executing the exploit.

Preparation Phase

  • Wallet 1 receives 1 ETH from Tornado Cash.
  • Wallet 6 receives 10 ETH (10 × 1 ETH) via Tornado Cash.
  • Wallet 6 bridges ETH to Arbitrum via Stargate.
  • Wallet 1 acquires 3.31700399 gmETH/ETH tokens through GMX.
  • Wallet 1 sets approvals and interacts with the gmETH/ETH Cauldron.
  • Wallet 1 distributes 0.5 gmETH/ETH tokens to Wallets 2, 3, 4, and 5.
  • Wallet 1 also sends 0.1 ETH to each of the above wallets.
  • Wallets 2–5 approve the master contract and deposit gmETH/ETH.

Attack Phase

  • Wallet 1 deploys the exploit contract: 0xf29120acd274a0c60a181a37b1ae9119fe0f1c9c
  • Wallet 6 funds the exploit contract with 9.93 ETH.
  • The attacker initiates a series of cook() transactions across multiple wallets and Cauldrons.
  • Each cook() includes deliberately failed GMX deposits, followed by a liquidation and reborrow sequence.

By the end of the campaign, the attacker drained ~$13.4 million across five GM Cauldrons via 56 transactions over the span of roughly 100 minutes.

Full activity log is available via the official Google Sheet from the post-mortem.
image

Market Impact

  • GMX Price: Dropped $55.20 → $46.92 (15.0% decrease)
  • MIM Spell Price: Dropped $1.20 → $1.08 (10.0% decrease)
  • Scope: Only the “gmCauldron” integrations were affected; other Abracadabra cauldrons remained unaffected
image

Vulnerability Exploitation

At its core, the bug stems from two critical design flaws in GmxV2CauldronV4 and its associated GmxV2CauldronRouterOrder contract:

  1. The [sendValueInCollateral](<https://github.com/Abracadabra-money/abracadabra-money-contracts/blob/dff69a19a219bbff90ab7b752c9f9c0ab5e8fe6f/src/periphery/GmxV2CauldronOrderAgent.sol#L241>)() function removes real tokens from the router during liquidations but does not update internal state variables like inputAmount, minOut, or minOutLong. This omission means the Cauldron believes the same amount of "potential collateral" is still present, even after some of it has been withdrawn.
1function sendValueInCollateral(address recipient, uint256 shareMarketToken) public onlyCauldron {
2        (uint256 shortExchangeRate, uint256 marketExchangeRate) = getExchangeRates();
3
4        uint256 amountShortToken = (degenBox.toAmount(IERC20(market), shareMarketToken, true) * oracleDecimalScale) /
5            (shortExchangeRate * marketExchangeRate);
6
7        shortToken.safeTransfer(address(degenBox), amountShortToken);
8        degenBox.deposit(IERC20(shortToken), address(degenBox), recipient, amountShortToken, 0);
9    }

The orderValueInCollateral() function continues to calculate the user's pending collateral using those unchanged internal fields. Since these values are never reduced when partial liquidations or failed deposits occur, the user appears to hold more collateral than they actually do — enabling fraudulent borrowing.

1 function orderValueInCollateral() public view returns (uint256 result) {
2        (uint256 shortExchangeRate, uint256 marketExchangeRate) = getExchangeRates();
3
4        if (depositType) {
5            uint256 marketTokenFromValue = (inputAmount * shortExchangeRate * marketExchangeRate) / oracleDecimalScale;
6            result = minOut < marketTokenFromValue ? minOut : marketTokenFromValue;
7        } else {
8            uint256 marketTokenFromValue = ((minOut + minOutLong) * shortExchangeRate * marketExchangeRate) / oracleDecimalScale;
9            result = inputAmount < marketTokenFromValue ? inputAmount : marketTokenFromValue;
10        }
11    }

Full Exploit Flow

image

Setup Phase: Failed GMX Deposit with Phantom Collateral

The attacker initiates a cook() call with an unrealistic minOut value when depositing collateral through GMX. This causes GMX to reject the deposit and return the input tokens (e.g., USDC) to the RouterOrder contract. Despite receiving back the tokens, the Cauldron still sees this failed deposit as having succeeded, due to the unchanged accounting fields.

image

Initial Borrow to Seed Funds

The attacker uses cook() to borrow a small amount of MIM, which is used to fund collateral that will later be recycled in the exploit.

image

Exploit Transaction

Precise cook() Steps The second cook() call contains multiple actions:

  • Action 5 (Borrow): Borrows MIM, pushing the loan-to-value ratio above the liquidation threshold.
  • Action 30 (Call): Calls the attacker's contract function (get_before_liquidate_amount) to compute how much collateral should be pulled during liquidation for maximum extraction.
  • Action 31 (Liquidate): Self-liquidates the position. The liquidation logic attempts to cover the debt first with BentoBox collateral, then pulls additional tokens from the attacker's RouterOrder via sendValueInCollateral(). These tokens are real USDC that had been returned from a failed GMX deposit.
  • Action 30 (Call): Calls another function in the attack contract (get_after_liquidate_amount) to compute how much MIM can now be borrowed against the unchanged phantom collateral.
  • Action 5 (Borrow): The attacker borrows again, this time backed by collateral that no longer exists but is still reported by orderValueInCollateral().
    • Action 30 (Call): Calls out to swap and extract all borrowed MIM from the protocol.
image

Final Solvency Check (Bypassed)

At the end of the cook() batch, the Cauldron invokes _isSolvent() to ensure the user remains solvent after all actions. However: As a result, the protocol still sees the user as solvent based on outdated internal state, and the transaction does not revert. The attacker walks away with both the borrowed MIM and real collateral that was seized during liquidation, while still appearing fully collateralized. _isSolvent() runs at the end of the cook() batch and falsely passes, as it uses the unchanged orderValueInCollateral().

  • _isSolvent() relies on orderValueInCollateral() from the RouterOrder,
  • This function continues to report the original, inflated collateral value based on inputAmount, minOut, or minOutLong,
  • These fields are never updated—even after a successful liquidation that pulled funds from the order.

As a result, the protocol still sees the user as solvent based on outdated internal state, and the transaction does not revert.

The attacker walks away with both the borrowed MIM and real collateral that was seized during liquidation, while still appearing fully collateralized.

_isSolvent() runs at the end of the cook() batch and falsely passes, as it uses the unchanged orderValueInCollateral().

image

Repeat and Drain The attacker can repeated this pattern across different Cauldrons and wallets, draining real assets each time while passing solvency checks based on invalid internal accounting.

Biggest Exploit Transaction (≈932 ETH)

How the attack could have been prevented

Below is a diff illustrating how sendValueInCollateral() might be patched to reduce the user’s “paper” collateral fields.

Note: This snippet is illustrative only. In a real production scenario, correctly fixing the vulnerability would require a deep understanding of the RouterOrder’s internal accounting logic, including how inputAmount, minOut, and minOutLong are used across all flows (e.g., deposits, liquidations, and cancellations). A comprehensive fix would also require updating orderValueInCollateral() and tracking closed or consumed orders correctly.
1 function sendValueInCollateral(address recipient, uint256 shareMarketToken) external onlyCauldron {
2     (uint256 shortExchangeRate, uint256 marketExchangeRate) = getExchangeRates();
3
4     uint256 amountShortToken =
5         (degenBox.toAmount(IERC20(market), shareMarketToken, true) * oracleDecimalScale)
6         / (shortExchangeRate * marketExchangeRate);
7
8+    // Example approach: Decrement the "inputAmount" or "minOut"
9+    // so that orderValueInCollateral() won't keep overstating the user's collateral
10+    if (depositType) {
11+         inputAmount = (inputAmount >= someEquivalentShort) ? (inputAmount - someEquivalentShort) : 0;
12+         if (minOut > someEquivalentShort) minOut -= someEquivalentShort;
13+     } else {
14+     // Similar logic adjusting (minOut + minOutLong) or inputAmount
15+     }
16
17     shortToken.safeTransfer(address(degenBox), amountShortToken);
18     degenBox.deposit(IERC20(shortToken), address(degenBox), recipient, amountShortToken, 0);
19 }
20

Additional Best Practices:

  1. Intermediate Solvency Checks: If the protocol’s cook() function is “batching” multiple actions, it could call _isSolvent() after each major step, not just at the end.
  2. Rigorous Audits: The introduction of a non‐atomic order system (with “paper” collateral) should have triggered deeper accounting checks.
  3. Restrict Self‐Liquidation: Some protocols disallow partial or self‐liquidation in a single transaction unless thoroughly verified.

Consequences

  • $13.4 Million in Undercollateralized MIM: Roughly 13.4M MIM was minted across five GM Cauldrons through 56 exploit transactions.
  • Duration: The attack spanned ~1 hour and 40 minutes, beginning at 07:57:52 AM UTC and ending at 09:37:36 AM UTC.
  • Market Impact:
    • GMX: Price dropped from $55.20 → $46.92 (−15.0%)
    • MIM Spell: Price dropped from $1.20 → $1.08 (−10.0%)
  • Operational Disruption: Borrowing was halted across all GM Cauldrons by 09:46:22 AM UTC to contain further damage.

The Protocol’s Response

Following discovery of the exploit, Abracadabra implemented emergency countermeasures and initiated recovery efforts:

  • Borrowing Disabled: All gmCauldrons were paused immediately after the exploit to prevent further damage.
  • OrderAgent Nullified: The orderAgent address was set to 0x000…000 to prevent further GMX deposit creation.
  • Rescue of Trapped Funds: Roughly $260,000 worth of assets still sitting in RouterOrder contracts post-exploit were successfully recovered.
  • Real-time Monitoring with Hexagate:
    • Hexagate was integrated to monitor Cauldrons, but DegenBox — the vault that held exploited assets — was not monitored.
    • In hindsight, including DegenBox would have enabled earlier detection and automatic response.
  • Multi-party Coordination: Security researchers, Guardian Audits, Chainalysis, and the Seal 911 community assisted in fund tracing and forensic analysis.
  • Communication & Bounty:
    • 20% Bounty Offered: A 20% bounty (~$2.58M) was announced for the safe return of the stolen funds.
    • The DAO announced it is awaiting communication from the attacker, both via on-chain messages and at [email protected].
image
Note: This is not the first time Abracadabra’s architecture has been exploited. Back in February 2024, an earlier vulnerability in its CauldronV4 debt accounting mechanism was used to extract over $6.4M.

Our team at Three Sigma published a detailed analysis of that incident, covering how share inflation allowed manipulation of internal borrow logic.

Read more: Abracadabra Money Exploit Analysis by Three Sigma - Feb 2024

Addresses

Affected Contracts

🧑‍💻Attacker Wallets

Frequently Asked Questions (FAQ)

1. What was the Abracadabra GMX exploit?

The Abracadabra GMX exploit refers to a $13 million attack on the protocol's GmxV2 CauldronV4, where an attacker manipulated internal accounting logic to appear solvent while extracting real collateral via self-liquidation.

2. How much was lost in the Abracadabra exploit?

Approximately $13 million worth of crypto assets (≈6,262 ETH) were drained from the protocol through a series of carefully constructed cook() transactions.

3. Was oracle manipulation involved in the Abracadabra hack?

No. The exploit did not involve oracle manipulation. The prices used in solvency calculations were accurate; the issue was stale collateral accounting in the RouterOrder contract.

4. What is GmxV2 CauldronV4?

GmxV2 CauldronV4 is a specialized lending market contract on Abracadabra that supports GMX V2's asynchronous deposit and withdrawal system, allowing users to borrow MIM against GMX LP positions.

5. How did the attacker bypass the solvency check?

The attacker exploited the fact that orderValueInCollateral() was never updated after partial liquidations. They borrowed MIM, self-liquidated, extracted real funds, and still passed _isSolvent() at the end of the cook() call.

6. What is the cook() function in Abracadabra contracts?

cook() is a batching function that allows users to execute multiple actions (e.g., deposit, borrow, withdraw, liquidate) in one transaction. Solvency is only checked at the end, which played a key role in the exploit.

7. What caused the inflated collateral values?

The sendValueInCollateral() function removed real tokens from the RouterOrder but failed to decrement internal values like inputAmount and minOut. This caused orderValueInCollateral() to return inflated numbers.

8. How did Abracadabra respond to the attack?

Abracadabra halted borrows on affected markets, coordinated with Chainalysis and other partners to trace funds, and offered a 20% bounty (~$2.58M) for the safe return of the stolen assets.

9. What can DeFi protocols learn from this exploit?

DeFi protocols should rigorously test edge cases involving asynchronous asset flows, ensure internal accounting matches actual token movements, and avoid relying solely on optimistic “paper collateral” during solvency checks.

Simeon Cholakov

Simeon Cholakov

Security Researcher