MUS refunded a first-deposit bonus in ETH and also credited the same bonus as withdrawable allocation, with no cap binding total payouts to the deposit, so a fresh deposit and withdrawal returned more ETH than was put in. DeFiHackLabs records a campaign total of about $36.9k on Ethereum.
Security ·
Logged as “2026-09-30: Deposit Refunds A First Deposit Bonus And Also Credits It As Withdrawable Allocation So A Single Deposit Withdraw From A Fresh Address Returns More Eth Than Deposited on MUS”.
The Mutual Uniting System, a pool on Ethereum known as MUS, paid a first-deposit bonus twice. It sent the bonus straight back to the depositor as ETH, and it also credited the same bonus as MUS allocation that a later withdrawal redeemed for ETH a second time. Nothing capped the two payments against the deposit that triggered them, so a depositor who arrived once and left once came away with more ETH than they put in. DeFiHackLabs records the incident and ships a proof of concept that replays one cycle against a fork of Ethereum.
The contract at 0x9bdf81e6066d32764b7e75a1b5577237e06d9364 is unverified, and DeFiHackLabs reversed it from its bytecode. Neither deposit() nor withdraw(pid, amount, withdrawRewards) carries a caller gate. The proof of concept finds no owner, admin or caller check in either function.
For an address depositing for the first time, deposit() does two separate things with the same first-deposit bonus. It calls ETH back to the caller immediately, which is the refund a first depositor is meant to receive. It also credits that caller a MUS allocation that withdraw() can redeem for ETH. The second step is the defect: the ETH has already left, so the allocation is a claim on ETH that the pool owes for money the depositor no longer has in it.
withdraw() honours that allocation in ETH, and never asks what the deposit cost. The missing check is a cap binding total ETH paid out to ETH actually deposited, or an accounting of the bonus that records it as spent once it has been refunded.
The first traced cycle in the sample transaction gives the arithmetic in full:
53.355703172210117028 ETHdeposit(), 32.253522567601017211 ETHwithdraw(), 21.502348378400678141 ETH53.755870946001695352 ETH0.400167773791578324 ETHThe excess is under one percent of the deposit. What matters is that it is positive, and that it repeats.
The sample transaction ran sixteen such cycles from sixteen fresh sub-addresses, summing to about 5.9476 ETH on the same measure. MUS's own standing balance was 5.335 ETH in the block before, and it reached zero across the attack block. No other user's deposits made up the difference: every transaction in block 26,087,602 that touched MUS was sent from the attacker's address.
Two figures exist for this incident and they do not measure the same thing. DeFiHackLabs records a campaign total of about $36.9k, which covers an unknown number of transactions across many addresses. The proof of concept explicitly warns that this total cannot be derived from the three sample transactions it has.
The figure it does reproduce exactly is the per-cycle net of 0.400168 ETH. That is the number to hold onto, because it is derived from a real call trace rather than extrapolated.
The sample transaction at block 26,087,602 ran from the attacker's address into a helper contract that drove the sixteen fresh sub-addresses. Three further attacker transactions in the same block touched MUS: 0xc6116f5f before it, and 0xaa172fca and 0x905af4e8 after it. The proof of concept counts sixteen cycles in 0xaa172fca as well.
One caveat on the mechanism is worth stating plainly. A cycle does not pay out against an idle pool. At the pool's plain state before the block, a single cycle loses ETH rather than gaining it, because the round that makes withdraw() pay out is one funded by deposits earlier in that same block. Those deposits were the attacker's own: 0xc6116f5f, which sent 10.7 ETH into the helper contract, is the only earlier transaction in the block to touch MUS. The attacker funded the round and then cycled fresh addresses against it.
Crediting a bonus that has already been paid requires the contract to know the bonus was paid. deposit() treats the refund and the allocation as independent, so the pool carries the cost of both.
Paying out the same entitlement twice is a familiar shape. Revamp released tokens from claim without debiting the caller's recorded balance, and X7Finance counted the same unaccounted balance into totalRewards on every empty claimRewards call. MUS creates the duplicate inside deposit() rather than at claim time, which is what allows a single deposit and a single withdrawal to complete the whole cycle.
Query every incident yourself — free, no signup
Updated 11 Nov 2026. Figures are as reported by the sources listed above and are not independently verified.