DAO Maker Vesting Contracts Unauthenticated init() / emergencyExit() Drain (2021-09-04)
- Name: DAO Maker vesting contracts unauthenticated
init()/emergencyExit()drain - Date: 2021-09-04
- Protocol: DAO Maker Vesting
- Chain: Ethereum
- Category: unprotected initializer / re-initialization ownership takeover / emergency withdrawal drain / shared implementation exposure
- Loss: about $4M, commonly described as the combined value of drained DERC, CPD, CAPS, and SHO vesting inventories sold into DAI
- Summary: DAO Maker’s vesting system used a shared implementation at
0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2whoseinit()path remained callable without authenticating who was allowed to perform setup. SlowMist’s decompilation of that implementation highlightedinit()selector0x84304ad7as the unauthenticated ownership-setting surface. On 2021-09-04, the attacker calledinit()against multiple live vesting contracts, made themselves owner, then invokedemergencyExit()to pull out vested project tokens that should have remained locked for beneficiaries. One public onchain example is DERC-related tx0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c, where the attacker called selector0x84304ad7against vesting agency0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940and the receipt emittedOwnershipTransferredfrom0x95626473b6782292d20f1b07a85a8b7f6ab63677to attacker0x2708cace7b42302af26f1ab896111d87faeff92f. The stolen allocations from Ternoa, Coinspaid, DeRace, and Showcase were then dumped through 1inch and other swap routes for DAI. - Trigger / entry point:
- DAO Maker project vesting inventories were held in contract instances backed by a common vesting implementation.
- The implementation exposed an
init()function (selector0x84304ad7) that public postmortems describe as unauthenticated. - That setup path could still rewrite privileged state on already-funded vesting instances.
- The contract owner could call
emergencyExit(), turning ownership capture directly into token withdrawal authority.
- Root cause:
- DAO Maker treated initialization as if it were a harmless deployment step rather than a root-of-trust ceremony.
init()did not prove that the caller was the legitimate deployer or authorized operator for the vesting instance.- Because
init()could set or replace owner-like control, the attacker did not need a second bug after calling it; the contract itself then recognized the attacker as the party allowed to runemergencyExit(). - The deeper failure was shared across several vesting instances at once because the same implementation and setup assumptions underlay multiple sale-token vesting contracts.
- Exploit path:
- The attacker identified DAO Maker vesting contracts backed by implementation
0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2, whoseinit()path public analyses describe as missing caller authentication. - The attacker called
init()on live vesting contracts that already held token balances for sale participants, making attacker-controlled state authoritative for those instances. In the publicly traced DERC path, tx0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7chit vesting agency0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940with selector0x84304ad7and immediately emittedOwnershipTransferredto the attacker. - With ownership seized, the attacker called
emergencyExit()on the affected vesting contracts. - Public reporting attributes four realized drains to this sequence:
13.5M CAPSfrom0x6e70c88be1d5c2a4c0c8205764d01abe6a3d2e22,5M CPDfrom0xd6c8dd834abeeefa7a663c1265ce840ca457b1ecacross two exits,1.44M DERCfrom0xdd571023d95ff6ce5716bf112ccb752e86212167, and about20.6M SHOfrom0xa43b89d5e7951d410585360f6808133e8b919289. - The attacker then sold the drained vesting inventory into DAI, with public reports highlighting 1inch swap transactions for CAPS, CPD, and DERC and a MetaMask Swap Router sale for SHO.
- Rekt also noted that after the first visible drain sequence,
init()was used against two more vesting-related contracts, though those later token extractions appear to have been at least partly recovered by a separate address in a likely rescue / whitehat sequence.
- The attacker identified DAO Maker vesting contracts backed by implementation
- What the attacker needed to be true:
- Live vesting contracts had to remain re-initializable after deployment and funding.
init()had to be able to assign or overwrite owner-level authority rather than only configure harmless metadata.emergencyExit()had to trust the current owner absolutely and transfer the held token inventory out immediately.- The affected vesting contracts had to already contain substantial sale-token balances.
- There had to be enough market liquidity to liquidate the drained allocations before recovery measures could contain them.
- Technical anatomy worth remembering:
- This is a clean example of initialization itself being the exploit, not merely a prelude to some later privilege abuse. The DERC-linked init tx left onchain evidence of that directly: the attacker did not need an obscure storage collision or governance bundle;
init()itself rewrote ownership on a live funded vesting path. - The shared-implementation aspect mattered: one bad initialization pattern replicated across several vesting contracts and let the same exploit recipe drain several unrelated project allocations.
emergencyExit()was not the original bug. It was the monetization path exposed onceinit()let the attacker rewrite who counted as owner.- The case is especially instructive because vesting systems often look operational rather than security-critical, yet here the vesting admin surface was a direct custody surface for locked token inventory.
- This is a clean example of initialization itself being the exploit, not merely a prelude to some later privilege abuse. The DERC-linked init tx left onchain evidence of that directly: the attacker did not need an obscure storage collision or governance bundle;
Three governance-authority promotions this incident collapsed
-
Some funded vesting instances and their setup / rescue selectors still existed as live authority objects
- DAO Maker had already-funded vesting contracts deployed behind a shared implementation, and those contracts still exposed a callable
init()path plus owner-onlyemergencyExit(). - That was only the weakest fact:
a vesting locker exists,it holds sale inventory, andconstructor-like plus emergency verbs are still reachable.
- DAO Maker had already-funded vesting contracts deployed behind a shared implementation, and those contracts still exposed a callable
-
That setup path was over-promoted into canonical runtime owner truth
- The protocol treated
init()as if it still carried legitimate setup meaning on live funded instances instead of demanding that initialization authority had already been irreversibly spent. - Once the attacker called selector
0x84304ad7, the contracts themselves emittedOwnershipTransferredand accepted the attacker-authored owner state as ordinary administrative truth. - The real collapse was not merely
missing access control; it was letting a deployment-time ceremony keep publishing who counted as the rightful vesting operator after funding.
- The protocol treated
-
That counterfeit owner state still inherited full vesting-inventory withdrawal authority
- After
init()replay,emergencyExit()did exactly what the protocol had designed it to do for a trusted owner: transfer the locked project-token inventory out immediately. - The system did not distinguish between
someone can write owner-like state,that state deserves to count as live operator authority, andthat accepted operator authority may now sweep the whole vesting balance. - The drain transactions were therefore not a second independent auth bug; they were the intended emergency-admin path faithfully honoring already-counterfeit ownership truth.
- After
- Representative transactions and addresses:
- Attacker EOA highlighted in public vesting analyses:
0x2708cace7b42302af26f1ab896111d87faeff92f - Likely later rescue / whitehat address highlighted by Rekt:
0xcf28556ee95be8c52ad2f3480149128cca51dac1 - Vesting implementation contract:
0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2 - DERC-related vesting agency contracts highlighted by SlowMist:
0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940,0xdd571023d95ff6ce5716bf112ccb752e86212167 - Public DERC
init()ownership-takeover tx:0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c - CAPS vesting contract /
emergencyExit()tx:0x6e70c88be1d5c2a4c0c8205764d01abe6a3d2e22/0xcb5be97496995d58da6f97491845040547b878e53a7b71f907a13408f3a54e5f - CPD vesting contract /
emergencyExit()txs:0xd6c8dd834abeeefa7a663c1265ce840ca457b1ec/0x4c273c2403aafd97e4b553f0e381cf1c63e5f2efebbe2ded7642a06f2b68c879,0xec2115ddb7f5020b2410978bb13cd23a9a38291d35be310d7825e7faeb6df1f4 - DERC vesting contract /
emergencyExit()tx:0xdd571023d95ff6ce5716bf112ccb752e86212167/0x1692a57f19b5e8e4bc6a372ac3c83c77cd4a1ea78414377ea66d3d59f4a7d2b7 - SHO vesting contract /
emergencyExit()tx:0xa43b89d5e7951d410585360f6808133e8b919289/0xdd0176475165b83c702d49a876d4dc888b73477ad8833582c72aa6ca5e0bacc3 - Representative token-sale swap txs: CAPS
0xbf38346aacf261f5e169a87ed874c33c21efb060c4a393e2b1443a3ac5d6e3fd, CPD0x3436af2c84d67254a4b81adc350c91d1b98ae52b2ff84645d14d4245c2d08c27, DERC0xc586a6b94e09556abf46ae3aa8cffa8e46dfcb0c22bce0b024d5e01743ceba9e, SHO0x76163daf6cf0c815c02fb1a98f5c6283ee7a922cbad41218eb7a6452c91824c8
- Attacker EOA highlighted in public vesting analyses:
- Response / mitigation:
- DAO Maker and affected projects publicly acknowledged the incident, while third-party analysts catalogued the impacted vesting inventories and sale transactions.
- Rekt observed that after the first visible drain sequence, a second address (
0xcf28556ee95be8c52ad2f3480149128cca51dac1) used the sameinit() -> emergencyExit()primitive against additional vesting-related contracts, then returned extracted tokens and transferred ownership, which strongly suggests belated rescue / whitehat containment once the initialization bug was understood. - The lasting lesson was that vesting instances and their initialization flows must be treated as privileged custody systems, not as low-risk distribution utilities.
Pattern tags
live funded vesting instances still accepted constructor-like setup, so initialization itself became the ownership-takeover primitiveshared implementation multiplied blast radius because one unauthenticated init pattern exposed several unrelated token-sale inventoriesemergency withdrawal logic was not independently broken; it faithfully monetized attacker-owned admin state after init replayvesting admin and deployment ceremony were effectively custody controls, even though the product surface looked like token distribution infrastructure
Why the exploit worked
- DAO Maker left a constructor-like trust ceremony alive on production vesting contracts that already held user-facing token inventory. That collapsed the line between
deployment-time setupandruntime admin authority. The DERC-linkedinit()receipt makes the collapse painfully concrete: the same call that should only ever have belonged to deployment emitted a liveOwnershipTransferredevent in production. - The decisive bug was not subtle storage aliasing or governance capture.
init()itself could still assign owner-like authority on live instances, so the attacker could make the contracts truthfully believe they were the authorized operator. - Once ownership moved,
emergencyExit()became an intended privileged withdrawal path rather than a second exploit step. The drain path was therefore as strong as the protocol’s own emergency-admin design. - The shared implementation made the incident operationally worse: one broken initialization assumption repeated across multiple project vesting contracts, so the attacker could run the same playbook against several token lockers in sequence.
Detection and audit pivots
- Enumerate every funded vesting or lockup instance and ask whether any
init,initialize,setup, or owner-assignment selector remains callable after deployment. Treatdistribution infraas a custody audit surface, not a back-office utility. - Compare implementation, clone, and instance addresses separately. A safe-looking vesting front contract is not enough if a shared implementation still exposes constructor-refactor helpers that can rewrite privileged state.
- Trace every path from owner capture to immediate monetization:
emergencyExit(), rescue functions, arbitrary token sweeps, beneficiary replacement, and pause or migration helpers. If init replay gives access to any of them, the exploit is economically complete. - Monitor for post-launch initialization behavior on live lockers: fresh
Initialized-style events, owner changes shortly before emergency exits, or repeated init selectors hitting already-funded contracts should already page responders.
Failure-prevention checklist
- Make deployment and initialization atomic for vesting, escrow, and token-lock contracts. A locker should never be funded before the operator set and initialization guard are irreversibly anchored.
- Permanently extinguish deployment-only setup selectors on live instances and on shared implementations. If clones or proxies need setup helpers, the code objects backing them should still be directly inert once production funding exists.
- Treat
emergencyExit()and related rescue paths as balance-sheet withdrawal authority. They need the same threat model as treasury transfer functions because ownership capture immediately turns them into drain rails. - Add invariants proving that owner/admin can only be set once, that init cannot be replayed through alternate selectors or shared implementations, and that funded vesting contracts can never revert to a fresh-looking state.
- Wire production monitoring around funded uninitialized contracts, unexpected owner changes, and any direct call into implementation addresses that should only ever serve as passive code templates.
Audit prompts
- Which exact address is supposed to anchor trust for each live vesting instance, and what proves that setup authority cannot be replayed after funding?
- Can any implementation, clone template, migration helper, or fallback-routed selector still write owner or beneficiary authority on a contract that already holds user assets?
- If an attacker became owner for one block, what immediate value-moving verbs become available:
emergencyExit(), token rescue, beneficiary rewrite, migration, or arbitrary execution? - Are deployment scripts guaranteeing one-step initialization everywhere the system was rolled out, or merely assuming an operator will race nobody to complete setup later?
- What telemetry would tell operators that a vesting contract just behaved like a fresh deployment after months of holding balances?
Lessons that should already be standard
- Vesting systems are not low-risk utility contracts. If they custody locked inventory, their admin and setup paths are part of the protocol’s live custody surface.
Emergencywithdrawal functions do not need their own bug to become catastrophic; they only need ownership to move once.- Shared implementations amplify boring initialization mistakes into portfolio-wide incidents. Reused code means reused blast radius.
- Constructor logic that survives as a callable runtime function is still privileged code, no matter how mundane the product wrapper sounds.
- A funded contract whose initialization state is still ambiguous is already compromised in principle, even before an attacker proves it in production.
Why this incident matters
- It is a compact, high-signal example of
unprotected initializerturning directly into custody loss without any need for fancy proxy mechanics or multi-step governance abuse. - It preserves the multi-instance blast-radius problem: one flawed vesting implementation pattern exposed several separate token-sale inventories at once.
- It also shows why
emergencyadmin functions deserve the same scrutiny as withdrawal or upgrade functions, because once ownership is captured they usually become the fastest drain path.
Internal linkages
- Core recurring pattern: unprotected-initializer-reinitialization.
- Positive-design companion: implementation-and-singleton-direct-call-deauthorization — useful when the sharper question is how a shared vesting implementation should have been made directly harmless before many funded vesting instances trusted it.
- Positive-design companion: decommissioning-authority-extinguishment-discipline — useful when the sharper lifecycle question is how deployment-only setup selectors like
init()should have been provably extinguished once funded vesting instances went live. - Shared-implementation cousin: parity-multisig-wallet-initwallet-ownership-reset-2017-07-19 — both incidents turned constructor-like setup that should have been one-time into a live privilege-escalation surface across many dependent instances.
- Live-proxy replay cousin: audius-governance-storage-collision-reinitialization-takeover-2022-07-23 — Audius needed storage-slot aliasing to reopen
initialize(), while DAO Maker’s vesting system appears to have left the setup path directly unauthenticated.
Control surface
-
The decisive leverage sat in DAO Maker’s vesting
init()reachability, the owner slot it could rewrite, and the fully trustedemergencyExit()path that converted recovered admin rights into immediate token custody. -
The system did not mainly fail at token-lock logic. It failed at the setup ceremony that decided who controlled the locker in the first place.
-
Sources:
- https://rekt.news/daomaker-rekt
- https://slowmist.medium.com/intelligence-of-slowmist-zone-dao-makers-vesting-system-was-hacked-5825e4828969
- https://smartcontractshacking.com/hacks/dao-maker-vesting-hack-2021
- https://x.com/Mudit__Gupta/status/1434059922774237185
- https://etherscan.io/tx/0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c
-
Last reviewed: 2026-08-03 UTC