These 200 ETH consisted of four lots of 50 ETH that the contract had won in MakerDAO auctions with zero bids during the «Black Thursday» liquidations of March 2020, but had never finally claimed.
One of the old contract's unprotected functions did not check who was calling it. This allowed the attacker to hand a contract under their control full control over this contract's account in the MakerDAO system. In a single transaction, the attacker settled the old auctions and withdrew the collateral held there.
Key transactions (times in UTC)
| Time | TXID | Description |
|---|---|---|
| 2026-10-06 05:57:23 | 0xd4c0b18a058e7f0a12855f30174f4cb1e973c15c886063deab97e1a21f048ac6 | Withdrawal of 0.1 ETH from Tornado Cash. This was the only funding this account received. |
| 2026-10-06 06:12:11 | 0x4a587af4213cc345c4c109fbf5fec46f9643183f91a9edf60305380f31ad1167 | Deployment of the attack contract used to exploit the vulnerability. |
| 2026-10-06 06:13:11 | 0xbb6940f7c2a1e68cafbae7bb9b94d09af9af06ec3a114f6996f2cab993f3a88c | The attack transaction. The attack contract obtained delegated permissions through the keeper function 0x8804d1de, settled auctions 1457–1460 and sent 200 ETH to the attacker's externally owned account (EOA). |
| 2026-10-06 06:19:35 | 0xb8ecff9c129d8d9331c85558eaeddff29a38c4c16b71a40986acbf8f6febefeb | The first of nine 10 ETH deposits into the Tornado Cash router. |
Key addresses
| Address | Role |
|---|---|
| 0x01EB957E5C7DcDDD60F3C875956cCc6fb9BdA5FA | The attacker's externally owned account (EOA). |
| 0xEc997d2aD033277913d6002277353368E8321dcF | The attack contract. |
| 0xf09a13072Ed939B79Bc25B66AA3a836ea6DCC170 | The malicious adapter module, which still holds delegated permissions over the victim's account in Vat. |
| 0x9c05a05893Ada984FC20D0DA0c046De5Cc0e8273 | The vulnerable contract (old auction keeper, AdminUpgradeabilityProxy). |
| 0x68399ed8aa33C5b43F863EE6782de492006A5546 | The vulnerable contract implementation (source code not verified). |
| 0xaDC374E77b89Af0B8B39F7c47E8e0A37B6DaF073 | The keeper's owner and operator in March 2020. |
| 0xd8a04F5412223F513DC55F839574430f5EC15531 | MakerDAO ETH-A Flipper, a decommissioned auction mechanism for selling collateral. |
| 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B | MakerDAO Vat, the core accounting module. |
| 0x2F0b23f53734252Bda2277357e97e1517d6B042A | MakerDAO ETH-A GemJoin, the exit point for the collateral asset. |
| 0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b | The Tornado Cash router, the address that received the stolen funds. |
| 0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc | The Tornado Cash 0.1 ETH pool, the source of the attacker's initial funding. |
How the attack unfolded
Module deployment
The exploit began with the attack contract creating an adapter module at 0xf09a13072Ed939B79Bc25B66AA3a836ea6DCC170. On creation, the module received the following parameters: the Flipper, Vat, ETH-A GemJoin, the collateral type (ilk), WETH, a payout address and four auction IDs.
Handing over permissions
The attack contract called the keeper function 0x8804d1de and passed it the newly created adapter at 0xf09. The keeper then called the module's drip() and vat() functions, checked through Vat.can() whether permissions had already been granted to it, and called Vat.hope(). This gave the module unlimited permissions to manage the keeper's account in the Vat system.
The callback
The keeper then called the attack contract's join() function. This handed control to code written by the attacker at a moment when the new permissions were already in effect.
Settling the auctions
Inside the callback, the module called the deal(id) function of the decommissioned ETH-A Flipper for auctions 1457, 1458, 1459 and 1460. In March 2020, the keeper had originally won each of the four 50 ETH lots with a zero bid, but had never settled the deals. Once the auctions were settled, 200 ETH was credited as ETH-A collateral to the keeper's account in Vat.
Withdrawing the funds
The module read the keeper's collateral balance and used the permissions it had obtained to move all 200 ETH from the keeper's account in Vat to its own address with the flux() function. It then withdrew the collateral through ETH-A GemJoin as 200 WETH and sent the WETH to the attack contract.
What lies at the core of the vulnerability?
The root cause of the attack was the lack of access control on the function 0x8804d1de in the keeper implementation at 0x68399ed8aa33C5b43F863EE6782de492006A5546.
All of the contract's other privileged functions, including the keeper's wrappers for the hope, flux and move operations, first run a ds-auth check and reject unauthorized calls with the error ds-auth-unauthorized.
The function 0x8804d1de, by contrast, accepts any address as an adapter, calls Vat.hope() for that address if the keeper has not yet granted it permissions, and then calls join() on the same address.
Because the keeper then called join(), the attacker's module was able to run its own code at a moment when these permissions were already in effect.
The four unused lots from 2020 made this vulnerability profitable for the attacker. Anyone can call the deal function for an auction.
DeFiTuna taught to round: a logic error cost $570 thousand in USDCJul 20, 2026Read moreMovement of funds
The attacker received all 200 ETH directly within the transaction used to exploit the vulnerability. At 06:19:35 UTC, just six minutes later, the attacker began moving the funds into Tornado Cash, sending them as 20 deposits of 10 ETH.

