CauldronV4 solvency-check bypass enables uncollateralized MIM borrowing
Exploit Transactions
0x842aae91c89a9e5043e64af34f53dc66daf0f033ad8afbf35ef0c93f99a9e5e6Victim Addresses
0xd96f48665a1410c0cd669a88898eca36b9fc2cceEthereum0x46f54d434063e5f1a2b2cc6d9aaa657b1b9ff82cEthereum0x289424add4a1a503870eb475fd8bf1d586b134edEthereumLoss Breakdown
Similar Incidents
bZx/Fulcrum iETH oracle manipulation enables undercollateralized WETH borrowing
33%NFTX Doodles collateral accounting flaw enables flash-loan ETH extraction
32%Indexed Finance DEFI5 gulp/reindex bug enables SUSHI flash-swap drain
32%WETH Drain via Unprotected 0xfa461e33 Callback on 0x03f9-62c0
31%SorraV2 staking withdraw bug enables repeated SOR reward drain
31%PumpToken removeLiquidityWhenKIncreases Uniswap LP Drain
31%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.totalandborrowLimit.borrowPartPerAddressset to the maximumuint128(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_BORROWsetstatus.needsSolvencyCheck = trueafter calling_borrow, which increasesuserBorrowPart[msg.sender]and transfers MIM-denominated DegenBox shares to the borrower. - For any non-core action code,
cookcalls_additionalCookAction(action, status, ...)and then assignsstatus = returnStatus. - In the base implementation,
_additionalCookActionignores the incomingstatusand returns a default-initializedCookStatusstruct with all fields set to zero, includingneedsSolvencyCheck.
As a result, a cook sequence of the form:
ACTION_BORROW(setneedsSolvencyCheck = true, increaseuserBorrowPart, move shares to borrower);- 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 maximumuint128in 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
_additionalCookActionimplementation returns a freshCookStatusstruct instead of threading through the existing one, causing loss of theneedsSolvencyCheckintent 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.
- Sender of the exploit transaction
-
Helper contract 0xB8e0A4758Df2954063Ca4ba3d094f2d6EdA9B993
- Deployed by the EOA in tx
0x842a...(metadata showsto = nullandfrom = 0x1aaade...). - Within the same transaction, interacts with DegenBox
0xd96f48665a1410c0cd669a88898eca36b9fc2cce, CauldronV4 clones0x46f54d4...and0x289424a..., Curve MIM-3CRV0x5a6a4d5..., Curve 3pool0xbebc447..., Uniswap V3 router/pools, and WETH90xc02aaa3.... - Accumulates MIM, then trades into WETH and receives
395059753040555107478wei from WETH9 before self-destructing and returning value to the creator EOA.
- Deployed by the EOA in tx
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
-
Adversary initial funding
- Several prior transactions (e.g.,
0x592468ca5c...,0x01c0fb6d37...,0x50af0b9f23...) transfer ETH into0x1aaade...in blocks 23504393–23504404, providing sufficient balance to cover the gas cost of the exploit transaction. - Evidence:
txlist_normal.jsonfor0x1aaade...inartifacts/root_cause/data_collector/iter_1/address/1/0x1aaade.../.
- Several prior transactions (e.g.,
-
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) showsfrom = 0x1aaade...andto = null, confirming contract creation. debug_trace_callTracer.jsonidentifies the created contract as0xB8e0A4758Df2954063Ca4ba3d094f2d6EdA9B993.
- In block 23504546, the adversary sends a type-2 contract-creation transaction (
-
Exploit execution and profit realization (within the same tx)
- The helper contract calls
CauldronV4.cookon clones0x46f54d4...and0x289424a...with action sequences that:- Borrow MIM via
ACTION_BORROW, increasinguserBorrowPartfor 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, causingCookStatus.needsSolvencyCheckto be reset to false.
- Borrow MIM via
- Because
status.needsSolvencyCheckis false by the end of the loop, the finalrequire(_isSolvent(msg.sender, _exchangeRate))is not executed, even thoughuserBorrowPart > 0anduserCollateralShare == 0for 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
395059753040555107478wei 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.
- The helper contract calls
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
1793766133547645000000000MIM 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.
- DegenBox’s on-chain MIM balance decreases by
-
Adversary profit in ETH
- For EOA
0x1aaade...:before_wei:173504352583541000after_wei:395232980108896905054delta_wei:395059475756313364054(≈395.059475756313364054 ETH)
- Tx metadata for
0x842a...showsgasUsed = 940264andgasPrice = 132247681wei, yielding a fee of124347733527784wei (0.000124347733527784 ETH). - The net ETH-denominated portfolio change for the adversary cluster is therefore a profit of approximately 395.05935 ETH after gas.
- For EOA
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 clones0x46f54d4...and0x289424a..., MagicInternetMoneyV1 (MIM)0x99D8a9C4..., Curve MIM‑3CRV0x5a6a4d5..., Curve 3pool0xbebc447..., Uniswap V3 router/pools, WETH90xc02aaa3.... - Configuration: Verified sources and contract-call snapshots (
artifacts/root_cause/data_collector/iter_2/contract/1/.../DegenBox.sol,iter_3/contract/1/.../CauldronV4.sol, anditer_4/contract_call/1/*/calls.json) show the deployed code and parameters, including the vulnerablecook/_additionalCookActionpattern 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 CauldronV4cookcalls on0x46f54d4...and0x289424a..., DegenBox interactions, and AMM swaps.
- The calldata used in
cookand 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.
- A single adversary-crafted transaction on chainid 1:
-
Inclusion feasibility
- The transaction is a standard type‑2 EOA transaction with nonzero
maxFeePerGasandmaxPriorityFeePerGas, 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).
- The transaction is a standard type‑2 EOA transaction with nonzero
-
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).