All incidents

CauldronV4 solvency-check bypass enables uncollateralized MIM borrowing

Share
Oct 04, 2025 12:54 UTCAttackLoss: 1,793,766.13 MIMManually checked1 exploit txWindow: Atomic
Estimated Impact
1,793,766.13 MIM
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Oct 04, 2025 12:54 UTC → Oct 04, 2025 12:54 UTC

Exploit Transactions

TX 1Ethereum
0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6
Oct 04, 2025 12:54 UTCExplorer

Victim Addresses

0xd96f48665a1410c0cd669a88898eca36b9fc2cceEthereum
0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82cEthereum
0x289424add4a1a503870eb475fd8bf1d586b134edEthereum

Loss Breakdown

1,793,766.13MIM

Similar Incidents

Root Cause Analysis

CauldronV4 solvency-check bypass enables uncollateralized MIM borrowing

1. Incident Overview & TL;DR

On Ethereum mainnet at block 23504546, an unprivileged adversary executed a single, attacker-crafted transaction (0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6) that deployed an ephemeral helper contract and immediately used it to exploit a logic flaw in two Abracadabra CauldronV4 markets integrated with the DegenBox vault.
Within this transaction, the helper contract invoked CauldronV4.cook on two DegenBox-backed Cauldron clones, borrowed essentially all available MIM from each without posting any collateral, withdrew the borrowed MIM from DegenBox, and routed the funds through Curve and Uniswap V3 into WETH. WETH9 then sent the ETH proceeds to the helper, which self-destructed and forwarded the ETH back to the creator EOA.

Native balance diffs for the adversary EOA 0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d show its ETH balance increasing from 173504352583541000 wei to 395232980108896905054 wei, a net gain of 395059475756313364054 wei (approximately 395.0595 ETH), while paying only 124347733527784 wei (0.0001243 ETH) in gas for the exploit transaction. DegenBox’s on-chain MIM balance decreased by roughly 1,793,766.1335 MIM, which was moved into the Curve MIM-3CRV pool and ultimately converted into the adversary’s ETH profit.

The root cause is a solvency-check bypass in CauldronV4.cook. After an ACTION_BORROW step sets a CookStatus.needsSolvencyCheck flag and increases the borrower’s userBorrowPart, a subsequent custom action routed through _additionalCookAction returns a default-initialized CookStatus struct. Assigning status = returnStatus clears needsSolvencyCheck back to false. As a result, the final require(_isSolvent(msg.sender, _exchangeRate)) is skipped even when userBorrowPart > 0 and userCollateralShare == 0, allowing fully uncollateralized borrowing of MIM from the affected Cauldrons.

2. Key Background

Abracadabra’s DegenBox is a BentoBox-style vault that tracks ERC‑20 balances and shares. Protocols such as CauldronV4 hold both collateral and borrowed assets inside DegenBox, using share accounting while interacting with external AMMs and strategies.

CauldronV4 is a collateralized borrowing primitive that integrates with DegenBox to manage per-user collateral and debt. Users interact through a generic cook(uint8[] actions, uint256[] values, bytes[] datas) entry point, which sequences actions like deposits, borrows, repayments, and arbitrary external calls. This design allows flexible compositions but pushes significant responsibility into the cook action dispatcher.

Each CauldronV4 clone relies on an oracle (via ProxyOracle) to price collateral against debt. However, the fundamental solvency invariant is captured directly in _isSolvent: a borrower with non‑zero debt and zero collateral should always be considered insolvent, regardless of oracle prices. The relevant part of the verified CauldronV4 implementation is:

function _isSolvent(address user, uint256 _exchangeRate) internal view returns (bool) {
    uint256 borrowPart = userBorrowPart[user];
    if (borrowPart == 0) return true;
    uint256 collateralShare = userCollateralShare[user];
    if (collateralShare == 0) return false;
    // ... compare collateral value vs. borrowPart using exchangeRate ...
}

In the affected deployment, the two exploited Cauldron clones were configured with effectively unlimited borrow limits and non-restrictive parameters at block 23504546. Contract-call snapshots at that block show:

  • borrowLimit.total and borrowLimit.borrowPartPerAddress set to the maximum uint128 (3.402823669209384634633746074317682e38), making borrow caps non-binding.
  • Collateralization and liquidation parameters configured to standard high-leverage values, not to prevent uncollateralized positions if the solvency guard is bypassed.

The exploitable design pattern is that CauldronV4.cook tracks whether a solvency check is required via a CookStatus struct, but then delegates to _additionalCookAction in a way that allows this struct to be overwritten and the flag cleared after a borrow.

3. Vulnerability Analysis & Root Cause

3.1 Vulnerable cook / _additionalCookAction interaction

In the verified CauldronV4 source (artifacts/root_cause/data_collector/iter_3/contract/1/0x46f54d4.../source/src/cauldrons/CauldronV4.sol), the relevant definitions are:

struct CookStatus {
    bool needsSolvencyCheck;
    bool hasAccrued;
}

function _additionalCookAction(
    uint8 action,
    CookStatus memory,
    uint256 value,
    bytes memory data,
    uint256 value1,
    uint256 value2
) internal virtual returns (bytes memory, uint8, CookStatus memory) {}

function cook(
    uint8[] calldata actions,
    uint256[] calldata values,
    bytes[] calldata datas
) external payable returns (uint256 value1, uint256 value2) {
    CookStatus memory status;
    for (uint256 i = 0; i < actions.length; i++) {
        uint8 action = actions[i];
        if (!status.hasAccrued && action < 10) {
            accrue();
            status.hasAccrued = true;
        }
        // ...
        } else if (action == ACTION_REMOVE_COLLATERAL) {
            // ...
            status.needsSolvencyCheck = true;
        } else if (action == ACTION_BORROW) {
            // ...
            (value1, value2) = _borrow(to, _num(amount, value1, value2));
            status.needsSolvencyCheck = true;
        // ...
        } else {
            (bytes memory returnData, uint8 returnValues, CookStatus memory returnStatus) =
                _additionalCookAction(action, status, values[i], datas[i], value1, value2);
            status = returnStatus;
            // decode returnData into value1/value2 if requested
        }
    }
    if (status.needsSolvencyCheck) {
        (, uint256 _exchangeRate) = updateExchangeRate();
        require(_isSolvent(msg.sender, _exchangeRate), "Cauldron: user insolvent");
    }
}

Key points:

  • Actions such as ACTION_BORROW set status.needsSolvencyCheck = true after calling _borrow, which increases userBorrowPart[msg.sender] and transfers MIM-denominated DegenBox shares to the borrower.
  • For any non-core action code, cook calls _additionalCookAction(action, status, ...) and then assigns status = returnStatus.
  • In the base implementation, _additionalCookAction ignores the incoming status and returns a default-initialized CookStatus struct with all fields set to zero, including needsSolvencyCheck.

As a result, a cook sequence of the form:

  1. ACTION_BORROW (set needsSolvencyCheck = true, increase userBorrowPart, move shares to borrower);
  2. A custom action handled via _additionalCookAction;

will end the loop with status.needsSolvencyCheck == false, because the borrowed state persists in storage, but status has been overwritten with a zeroed struct returned from _additionalCookAction. The final if (status.needsSolvencyCheck) guard is then skipped entirely.

Given the _isSolvent definition, any user with userBorrowPart > 0 and userCollateralShare == 0 should be rejected as insolvent. Instead, the caller exits cook successfully in exactly this state.

3.2 Concrete post-state evidence from Cauldron clones

Contract-call snapshots at block 23504546 confirm that the exploited helper contract is left with debt and no collateral in both Cauldron clones. For the clone at 0x289424add4a1a503870eb475fd8bf1d586b134ed, calls.json shows:

  • userCollateralShare(0xB8e0A4758Df2954063Ca4ba3d094f2d6EdA9B993) = 0.
  • userBorrowPart(0xB8e0A4758Df2954063Ca4ba3d094f2d6EdA9B993) = 60557920478553930467368.
  • borrowLimit() returns the maximum uint128 in both fields, so borrow caps are not constraining this position.

The corresponding file for 0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c shows the same pattern: userCollateralShare for the helper contract is zero and userBorrowPart is very large. Under _isSolvent, these states are definitively insolvent but are not rejected because the final solvency check was skipped via the flawed CookStatus handling.

3.3 Root cause classification

The root cause is a protocol-level logic error in CauldronV4’s cook and _additionalCookAction design:

  • Critical invariants (solvency) are enforced only conditionally, based on a mutable in-memory flag that can be inadvertently cleared by extension hooks.
  • The default _additionalCookAction implementation returns a fresh CookStatus struct instead of threading through the existing one, causing loss of the needsSolvencyCheck intent set by previous actions.
  • With borrow limits effectively unbounded and large protocol-owned MIM balances in DegenBox, exploiting this bypass allows an attacker to borrow nearly all available MIM without posting any collateral.

This aligns with the ATTACK root cause category: an unprivileged adversary exploited a deterministic protocol bug to drain protocol-held liquidity for profit.

4. Adversary Flow Analysis

4.1 Adversary-related accounts

The adversary cluster consists of:

  • EOA 0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d

    • Sender of the exploit transaction 0x842a....
    • Funder and owner of the helper contract.
    • Final recipient of ETH profit via WETH9 transfers and the helper’s self-destruct.
    • Etherscan-style txlist (artifacts/root_cause/data_collector/iter_1/address/1/0x1aaade.../txlist_normal.json) shows inbound funding transfers immediately before block 23504546.
  • Helper contract 0xB8e0A4758Df2954063Ca4ba3d094f2d6EdA9B993

    • Deployed by the EOA in tx 0x842a... (metadata shows to = null and from = 0x1aaade...).
    • Within the same transaction, interacts with DegenBox 0xd96f48665a1410c0cd669a88898eca36b9fc2cce, CauldronV4 clones 0x46f54d4... and 0x289424a..., Curve MIM-3CRV 0x5a6a4d5..., Curve 3pool 0xbebc447..., Uniswap V3 router/pools, and WETH9 0xc02aaa3....
    • Accumulates MIM, then trades into WETH and receives 395059753040555107478 wei from WETH9 before self-destructing and returning value to the creator EOA.

There is no evidence of privileged roles or governance rights associated with either address; both behave as unprivileged participants in public contracts, satisfying the ACT adversary model.

4.2 Transaction trace highlights

The combined traces from debug_trace_callTracer.json and the MIM-focused subtrace (mim_subtrace_abi_decoded.json) illustrate the token flows. A representative MIM transfer segment is:

{
  "from": "0xd96f48665a1410c0cd669a88898eca36b9fc2cce",
  "to":   "0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3",
  "function": "transfer",
  "decoded_args": [
    {
      "name": "to",
      "type": "address",
      "value": "0xb8e0a4758df2954063ca4ba3d094f2d6eda9b993"
    },
    {
      "name": "amount",
      "type": "uint256",
      "value": 1793766133547645000000000
    }
  ]
}

This corresponds to DegenBox transferring approximately 1,793,766.1335 MIM worth of shares to the helper contract, which then approves and transfers those tokens through an intermediate address into the Curve MIM-3CRV pool.

The same traces show WETH9 ultimately sending 395059753040555107478 wei to the helper, and the helper’s self-destruct sending ETH back to 0x1aaade.... Native balance diffs in balance_diff.json confirm that there are no matching outbound ETH transfers from the adversary addresses that would offset this gain within the transaction.

4.3 Attack lifecycle

  1. Adversary initial funding

    • Several prior transactions (e.g., 0x592468ca5c..., 0x01c0fb6d37..., 0x50af0b9f23...) transfer ETH into 0x1aaade... in blocks 23504393–23504404, providing sufficient balance to cover the gas cost of the exploit transaction.
    • Evidence: txlist_normal.json for 0x1aaade... in artifacts/root_cause/data_collector/iter_1/address/1/0x1aaade.../.
  2. Helper contract deployment

    • In block 23504546, the adversary sends a type-2 contract-creation transaction (0x842a...) with 0 ETH value.
    • Seed metadata (seed/1/0x842a.../metadata.json) shows from = 0x1aaade... and to = null, confirming contract creation.
    • debug_trace_callTracer.json identifies the created contract as 0xB8e0A4758Df2954063Ca4ba3d094f2d6EdA9B993.
  3. Exploit execution and profit realization (within the same tx)

    • The helper contract calls CauldronV4.cook on clones 0x46f54d4... and 0x289424a... with action sequences that:
      • Borrow MIM via ACTION_BORROW, increasing userBorrowPart for the helper address and transferring DegenBox-held MIM shares from the Cauldrons to the helper.
      • Invoke one or more custom action codes routed through _additionalCookAction, causing CookStatus.needsSolvencyCheck to be reset to false.
    • Because status.needsSolvencyCheck is false by the end of the loop, the final require(_isSolvent(msg.sender, _exchangeRate)) is not executed, even though userBorrowPart > 0 and userCollateralShare == 0 for the helper in both Cauldrons.
    • The helper then uses DegenBox to withdraw the borrowed MIM, trades through Curve MIM‑3CRV and 3pool and Uniswap V3 into WETH, and receives 395059753040555107478 wei from WETH9.
    • The helper self-destructs and forwards the ETH to the creator EOA 0x1aaade..., which remains with a net balance increase of 395.0595 ETH after gas.

5. Impact & Losses

Balance diffs in seed/1/0x842a.../balance_diff.json quantify the impact precisely:

  • Protocol loss in MIM

    • DegenBox’s on-chain MIM balance decreases by 1793766133547645000000000 MIM units, corresponding to 1,793,766.13354764508484412 MIM at 18 decimals.
    • These MIM tokens are transferred into the Curve MIM‑3CRV pool and then swapped through AMM routes into WETH.
  • Adversary profit in ETH

    • For EOA 0x1aaade...:
      • before_wei: 173504352583541000
      • after_wei: 395232980108896905054
      • delta_wei: 395059475756313364054 (≈395.059475756313364054 ETH)
    • Tx metadata for 0x842a... shows gasUsed = 940264 and gasPrice = 132247681 wei, yielding a fee of 124347733527784 wei (0.000124347733527784 ETH).
    • The net ETH-denominated portfolio change for the adversary cluster is therefore a profit of approximately 395.05935 ETH after gas.

The Cauldron clones retain large userBorrowPart entries for the now-destroyed helper contract and zero corresponding collateral, leaving a persistent accounting hole: protocol-owned MIM liquidity has been drained from DegenBox, and outstanding debt is effectively uncollectible. Absent out-of-band remediation, this loss must be borne by the protocol or its broader stakeholder set.

6. ACT Opportunity Characterization

Under the ACT definition, this incident represents an “anyone-can-take” opportunity present on Ethereum mainnet at block 23504546.

  • System state (pre-state σ_B)

    • Chain: Ethereum mainnet, block 23504545 (immediately before the exploit block).
    • Contracts: DegenBox 0xd96f4..., CauldronV4 master and clones 0x46f54d4... and 0x289424a..., MagicInternetMoneyV1 (MIM) 0x99D8a9C4..., Curve MIM‑3CRV 0x5a6a4d5..., Curve 3pool 0xbebc447..., Uniswap V3 router/pools, WETH9 0xc02aaa3....
    • Configuration: Verified sources and contract-call snapshots (artifacts/root_cause/data_collector/iter_2/contract/1/.../DegenBox.sol, iter_3/contract/1/.../CauldronV4.sol, and iter_4/contract_call/1/*/calls.json) show the deployed code and parameters, including the vulnerable cook/_additionalCookAction pattern and effectively unlimited borrow limits.
  • Adversary transaction sequence (b)

    • A single adversary-crafted transaction on chainid 1:
      • txhash: 0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6
      • Sender: 0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d (EOA)
      • Behavior: contract creation of the helper at 0xB8e0A4..., followed by CauldronV4 cook calls on 0x46f54d4... and 0x289424a..., DegenBox interactions, and AMM swaps.
    • The calldata used in cook and AMM interactions is fully reconstructible from public ABI signatures, verified contract sources, and the recorded trace; no private order flow or privileged calls are required.
  • Inclusion feasibility

    • The transaction is a standard type‑2 EOA transaction with nonzero maxFeePerGas and maxPriorityFeePerGas, submitted to the public mempool and included on-chain.
    • Any unprivileged EOA with sufficient ETH to pay the observed gas cost could construct and submit an identical transaction using only:
      • Canonical contract addresses and ABIs (available on Etherscan and in the verified sources).
      • Public RPC endpoints or archive nodes to mirror the pre-state and calibrate parameters.
      • Standard transaction submission channels (public mempool or relay).
  • Success predicate (profit)

    • Reference asset: ETH.
    • Adversary address: 0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d.
    • Value before: 0.173504352583541 ETH.
    • Value after: 395.232980108896905054 ETH.
    • Delta: 395.059475756313364054 ETH.
    • Fees paid: 0.000124347733527784 ETH.
    • The net portfolio change is strictly positive and large in magnitude relative to fees, so the opportunity yields substantial ETH profit for any actor who successfully reproduces the sequence.

All components of this exploit—contract code, configuration, traces, and balances—are derived from canonical on-chain data and verified sources, and the adversary’s actions rely only on permissionless interactions. Consequently, this is a clear ACT opportunity arising from a deterministic CauldronV4 logic bug.

7. References

  • Seed transaction metadata, traces, and balance diffs for 0x842a... (artifacts/root_cause/seed/1/0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6).
  • DegenBox verified source (artifacts/root_cause/data_collector/iter_2/contract/1/0xd96f48665a1410c0cd669a88898eca36b9fc2cce/source/src/flat/DegenBox.sol).
  • CauldronV4 verified source (artifacts/root_cause/data_collector/iter_3/contract/1/0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82c/source/src/cauldrons/CauldronV4.sol).
  • Cauldron clone configuration and state at block 23504546 (artifacts/root_cause/data_collector/iter_4/contract_call/1/*/calls.json).
  • Adversary EOA tx history and funding (artifacts/root_cause/data_collector/iter_1/address/1/0x1aaade3e9062d124b7deb0ed6ddc7055efa7354d/txlist_normal.json).