Calculated from recorded token losses using historical USD prices at the incident time.
0x26a83db7e28838dd9fee6fb7314ae58dcc6aee9a20bf224c386ff5e80f7e4cf20x7259e152103756e1616A77Ae982353c3751A6a90Ethereum0xd96f48665a1410C0cd669A88898ecA36B9Fc2cceEthereumOn Ethereum block 19118660, transaction 0x26a83db7e28838dd9fee6fb7314ae58dcc6aee9a20bf224c386ff5e80f7e4cf2 exploited the Abracadabra CauldronV4 clone at 0x7259e152103756e1616A77Ae982353c3751A6a90. The attacker used a public DegenBox flashloan, corrupted Cauldron debt accounting, and then borrowed 5000047.849758731262099149 MIM while posting only negligible effective collateral. The exploit was permissionless and reproducible from public chain state.
The root cause was a deployed repayForAll(uint128,bool) path that allowed any caller to reduce totalBorrow.elastic without reducing totalBorrow.base or borrower debt parts. Once that rebase inconsistency existed, the standard round-up behavior in debt-part conversions and the Cauldron solvency formula understated attacker debt and enabled oversized borrowing.
CauldronV4 tracks system debt as a Rebase pair: elastic is total debt amount and base is total borrower part supply. Each borrower stores userBorrowPart. Solvency is then evaluated by converting a borrower's part back into elastic debt using the global totalBorrow ratio.
This exploit depended on a deployed Cauldron variant whose verified source exposed an extra repayForAll function. That function was not just a benign convenience method. In the deployed contract, it let a caller shrink global elastic debt while leaving the global part supply intact, which breaks the core invariant that outstanding debt parts must continue to represent pro-rata claims on real debt.
The rounding behavior from BoringRebase is also essential. When elastic becomes tiny while base remains positive, converting a tiny borrow amount into borrow parts rounds up aggressively, but repaying a tiny part only burns a tiny elastic amount. That asymmetry becomes exploitable once the rebase has already been corrupted.
The vulnerability is an accounting attack against Cauldron debt rebase state. A permissionless caller can invoke repayForAll with skim=true after transferring MIM into the Cauldron, causing the contract to deposit those tokens into DegenBox and subtract only the chosen amount from totalBorrow.elastic. The function does not reduce totalBorrow.base and does not reduce any userBorrowPart, so the ratio between debt amount and debt parts becomes invalid immediately.
After that corruption, repayments of selected public borrowers consume only tiny elastic amounts while burning large debt parts, which can drive the system to totalBorrow.elastic = 0 while leaving totalBorrow.base > 0. From that broken state, 1-unit borrows mint outsized borrow parts because toBase(..., true) rounds up against near-zero elastic debt. The protocol's _isSolvent check then prices debt as borrowPart * totalBorrow.elastic / totalBorrow.base, so inflating base while keeping elastic near zero makes the borrower's position appear far safer than it really is. That lets the attacker borrow millions of MIM against trivial collateral.
The trace shows the pre-exploit debt state immediately before the critical mutation:
CauldronV4::totalBorrow() -> elastic 248844382279231396727995, base 232357690628520295106240
During the flashloan callback, the attacker transferred 240000e18 MIM into the Cauldron and then called repayForAll(240000e18, true):
MagicInternetMoneyV1::transfer(CauldronV4, 240000000000000000000000)
CauldronV4::repayForAll(240000000000000000000000, true)
...
emit LogRepayForAll(
amount: 240000000000000000000000,
previousElastic: 248851138492208380186449,
newElastic: 8851138492208380186449
)
That event is the concrete breakpoint. elastic fell from 248851138492208380186449 to 8851138492208380186449, while base stayed at 232357690628520295106240. The rebase no longer reflected the debt parts already assigned to users.
The attacker then repaid a curated set of public borrowers by part using skim=true. Because the elastic/base ratio was already corrupted, each repayment burned large debt parts for tiny real debt amounts. The trace confirms the system eventually reached:
CauldronV4::totalBorrow() -> 0, 97
At that point the protocol had zero aggregate elastic debt but still had 97 aggregate debt parts outstanding, which should be unreachable in a sound accounting design.
The next step exploited BoringRebase rounding. The relevant public library logic is:
function toBase(Rebase memory total, uint256 elastic, bool roundUp) internal pure returns (uint256 base) {
if (total.elastic == 0) {
base = elastic;
} else {
base = (elastic * total.base) / total.elastic;
if (roundUp && (base * total.elastic) / total.base < elastic) {
base++;
}
}
}
function toElastic(Rebase memory total, uint256 base, bool roundUp) internal pure returns (uint256 elastic) {
if (total.base == 0) {
elastic = base;
} else {
elastic = (base * total.elastic) / total.base;
if (roundUp && (elastic * total.base) / total.elastic < base) {
elastic++;
}
}
}
With elastic at zero or near zero and base still positive, 1-unit borrow operations mint disproportionately large borrow parts, while 1-part repayments burn only 1 unit of elastic. The helper contract exploited exactly that state transition. The trace shows:
LogAddCollateral(... share: 100)
LogBorrow(... amount: 1, part: 1)
LogBorrow(... amount: 1, part: 98)
LogRepay(... amount: 1, part: 1)
LogBorrow(... amount: 1, part: 195)
This inflated the denominator used later by the solvency check without restoring meaningful elastic debt. Once the denominator was large enough, the main attacker contract borrowed 5000047849758731262099149 MIM and the protocol accepted the position.
The controlling EOA was 0x87f585809ce79ae39a5fa0c7c96d0d159eb678c9. Inside the seed transaction it deployed an orchestration contract at 0x193e045bee45c7573ff89b12601c745af739ce67, and that contract later deployed a helper at 0xE59B54a9E37ab69F6E9312a9b3f72539ee184e5A.
The execution sequence was:
300000 MIM from DegenBox via flashloan.240000 MIM into the victim Cauldron and call repayForAll(..., true), collapsing totalBorrow.elastic while preserving totalBorrow.base.userBorrowPart balances and repay selected borrowers with skim=true until totalBorrow becomes (0, 97).100 collateral shares and run repeated 1-unit borrow / 1-part repay loops, inflating debt parts under the broken ratio.5000047.849758731262099149 MIM from the victim Cauldron.150 MIM fee, and route remaining proceeds to the controlling EOA.The trace and balance diff show the final value transfer to the controller:
MagicInternetMoneyV1::transfer(0x87F585809Ce79aE39A5fa0C7C96d0d159eb678C9, 349003467479499865371154)
WETH9::withdraw(1807681416620123405036)
The directly measured victim-side token loss was borne by DegenBox's MIM inventory. The collector balance diff shows DegenBox MIM decreasing by:
-4751003467479499865371154
With 18 decimals, that is 4751003.467479499865371154 MIM. The controlling EOA also finished the transaction with 349003.467479499865371154 MIM and 1806.930663975869826494 ETH of native-value gain after the exit sequence.
The security failure is therefore not a mere pricing discrepancy or benign arbitrage. It is a protocol-accounting compromise that let an unprivileged actor create synthetic borrow capacity and extract several million MIM from protocol reserves.
0x26a83db7e28838dd9fee6fb7314ae58dcc6aee9a20bf224c386ff5e80f7e4cf20x7259e152103756e1616A77Ae982353c3751A6a900xd96f48665a1410C0cd669A88898ecA36B9Fc2cce0x99d8a9c45b2eca8864373a26d1459e3dff1e17f30x8078198Fc424986ae89Ce4a910Fc109587b6aBF3/workspace/session/artifacts/collector/seed/1/0x26a83db7e28838dd9fee6fb7314ae58dcc6aee9a20bf224c386ff5e80f7e4cf2/trace.cast.log/workspace/session/artifacts/collector/seed/1/0x26a83db7e28838dd9fee6fb7314ae58dcc6aee9a20bf224c386ff5e80f7e4cf2/balance_diff.jsonhttps://etherscan.io/address/0x7259e152103756e1616A77Ae982353c3751A6a90#codehttps://raw.githubusercontent.com/boringcrypto/BoringSolidity/master/contracts/libraries/BoringRebase.sol