Audius Governance Storage-Collision Re-initialization Takeover (2022-07-23)
- Name: Audius governance storage-collision re-initialization takeover
- Date: 2022-07-23
- Protocol: Audius
- Chain: Ethereum
- Category: unprotected initializer / re-initialization ownership takeover / proxy-storage collision / governance takeover
- Loss: 18,564,497 AUDIO transferred from the community treasury (about $6 million at the time of transfer; the attacker realized roughly 704 ETH after dumping)
- Summary: Audius ran governance, staking, and delegation contracts behind a custom
AudiusAdminUpgradeabilityProxythat storedproxyAdminin storage slot0. The proxied implementations inherited OpenZeppelin’s olderInitializable, which also expected its_initializedand_initializingbooleans in slot0. Because the governance contract address stored asproxyAdminended with bytes0xabac, both booleans decoded as truthy inside the implementation context. That made theinitializermodifier always succeed and, fatally, treat calls as non-top-level initialization so the flags never changed. The attacker repeatedly calledinitialize()on Governance, Staking, andDelegateManagerV2, rewired privileged contract pointers to an attacker-controlled governance contract, fabricated two enormous internal delegations of10,000,000,000,000 AUDIO, then used that fake voting power to pass proposal#85and transfer18,564,497 AUDIOout of the community treasury. - Trigger / entry point:
- The critical entry point was repeated
initialize()access on proxied Audius contracts that were supposed to be one-time initialized already. - Audius’s custom proxy stored
proxyAdminin slot0instead of using an isolated EIP-1967-style admin slot. - The proxied implementations inherited an
Initializablelayout that also used slot0for the two initialization booleans.
- The critical entry point was repeated
- Root cause:
- This was not a simple missing-initializer bug. The guard existed, but its state lived in a storage domain that collided with proxy metadata.
- In Audius’s deployment, slot
0of the proxy already contained the governance contract address0x4DEcA517D6817B6510798b7328F2314d3003AbACasproxyAdmin. - Under
delegatecall, the implementation read that same slot as_initializedand_initializing. The low bytes0xacand0xabmade both booleans non-zero. - Because
_initializingappeared true, theinitializermodifier treated calls as nested initialization rather than a top-level one, so it never reset or finalized the flags. Repeatedinitialize()calls therefore kept succeeding. - Once the attacker could re-run initialization on Governance, Staking, and
DelegateManagerV2, they could rewrite governance addresses, guardian / voting configuration, and the internal delegation state that determined proposal power.
- Exploit path:
- The attacker recognized that Audius proxy slot
0collided withInitializableslot0, makinginitialize()replayable on already-deployed contracts. - In transaction
0x3bbb15f9852c389e8d77399fe88b49b042d0f22aad4a33c979fbabc60a34b24f, they calledinitialize()across the affected contracts and made a first attempt to fabricate internal delegation state large enough to pass a malicious proposal. That attempt failed because the proposal did not receive votes. - In transaction
0xfefd829e246002a8fd061eede7501bccb6e244a9aacea0ebceaecef5d877a984, they repeated the sequence, again reinitializing Governance, Staking, andDelegateManagerV2. - During those replayed initialization calls, the attacker changed the Governance contract’s guardian settings and pointed the governance addresses inside Staking and
DelegateManagerV2at an attacker-controlled governance contract0xbdbb5945f252bc3466a319cdcc3ee8056bf2e569. - Using the corrupted staking / delegation state, the attacker recorded two bogus delegations of
10,000,000,000,000 AUDIOto themselves. No real tokens were minted, but Audius governance logic treated the altered storage as legitimate voting power. - With that fabricated quorum, the attacker pushed proposal
#85, which instructed the community treasury to transfer its AUDIO balance to the attacker-controlled address. - In transaction
0x4227bca8ed4b8915c7eec0e14ad3748a88c4371d4176e716e8007249b9980dc9, the treasury transfer executed and moved18,564,497 AUDIOto the attacker. - The attacker then dumped the stolen AUDIO in a single Uniswap transaction for about
704 ETH, turning a governance-storage exploit into realized theft.
- The attacker recognized that Audius proxy slot
- What the attacker needed to be true:
- The proxy had to store admin metadata in an implementation-colliding storage slot rather than an isolated admin slot.
- The implementations had to rely on
Initializablestate stored in that same colliding slot. - Governance, Staking, and
DelegateManagerV2each had to expose replayableinitialize()paths that wrote privileged configuration. - Audius governance had to accept staking / delegation storage as the source of voting truth, even though the apparent balances were only written into contract state rather than backed by real tokens.
- The treasury-transfer action had to be reachable through governance alone once quorum and proposal state were forged.
- Technical anatomy worth remembering:
- Audius is a useful case because the initializer guard was present. The failure sat one layer lower, in proxy / implementation storage layout.
- The memorable detail is the trailing
0xabacbytes of the governance address stored in slot0: those bytes made_initializingand_initializedlook permanently truthy. - The exploit turned a storage-layout bug into a control-plane seizure rather than a direct vault-drain primitive. The attacker first stole governance, then used governance to move treasury funds.
- The fake
10T AUDIOdelegations were internal accounting artifacts, not minted supply. The exploit path depended on governance trusting those mutated storage values as valid vote weight. - This incident is also a reminder that custom proxy tweaks around admin storage are security-critical. Deviating from EIP-1967-style reserved slots can quietly invalidate otherwise standard implementation assumptions.
Three governance-authority promotions this incident collapsed
-
Some slot-
0bytes already existed and some setup selectors were still reachable- The proxy already held a live
proxyAdminaddress in slot0, and the proxied Governance / Staking /DelegateManagerV2contracts still exposedinitialize()entry points. - That was only the weakest fact:
the shared storage word contains bytesandconstructor-like code can still be called.
- The proxy already held a live
-
Those bytes were over-promoted into canonical initialization truth
- Under
delegatecall, Audius treated the same slot-0word as OpenZeppelinInitializablestate. - Because the governance address ended in
0xabac,_initializingand_initializedboth decoded as truthy, so repeatedinitialize()calls kept looking like legitimate nested initialization rather than forbidden replay. - That mistaken freshness story let the attacker rewrite governance pointers, guardian state, and delegation accounting without ever needing real token movement.
- Under
-
That counterfeit governance state still inherited full treasury authority
- Once the attacker-authored staking and delegation state was accepted as real vote weight, proposal
#85was treated as ordinary community-governance output. - The treasury did not distinguish between
delegation records exist,those records are economically grounded vote truth, andthat accepted vote truth may now move 18.5M AUDIO in one step. - The final transfer tx was therefore not a separate auth failure; it was the treasury faithfully honoring already-counterfeit governance truth.
- Once the attacker-authored staking and delegation state was accepted as real vote weight, proposal
- Representative transactions and addresses:
- First malicious initialization / failed attempt tx:
0x3bbb15f9852c389e8d77399fe88b49b042d0f22aad4a33c979fbabc60a34b24f - Second malicious initialization / forged-vote tx:
0xfefd829e246002a8fd061eede7501bccb6e244a9aacea0ebceaecef5d877a984 - Treasury-drain tx:
0x4227bca8ed4b8915c7eec0e14ad3748a88c4371d4176e716e8007249b9980dc9 - Attacker-controlled governance contract:
0xbdbb5945f252bc3466a319cdcc3ee8056bf2e569 - Governance / proxyAdmin address whose slot-0 bytes caused the collision:
0x4DEcA517D6817B6510798b7328F2314d3003AbAC - Attacker EOA noted in public writeups:
0xa0c7BD318D69424603CBf91e9969870F21B8ab4c
- First malicious initialization / failed attempt tx:
- Response / mitigation:
- Audius and incident responders, including samczsun, determined the exploit was still live and raced to regain control using the same initialization weakness before a follow-on drain could happen.
- The team temporarily upgraded affected contracts to a blocking implementation that froze writes and, as a side effect, briefly halted token transfers while control was re-established.
- Audius then patched its initialization layout by adding padding so the implementation’s initialization state no longer overlapped proxy admin storage, and restricted initialization so only the proxy admin could perform the final recovery initialization.
- The team later restored the affected contracts and published a detailed postmortem plus recovery repo documenting the takeover fix.
Pattern tags
- storage-layout-collision
- unprotected-initializer-reinitialization
- Proxy metadata in slot
0aliased OpenZeppelinInitializablestate, so a standard-looking one-time guard authenticated the wrong storage domain - Replayed
initialize()calls rewired governance pointers and delegation state rather than directly stealing treasury funds on the first call - The treasury drain was a second-stage monetization step after governance truth had already been corrupted
Why the exploit worked
- Audius’s proxy and implementation disagreed about what slot
0meant. The proxy thought it storedproxyAdmin; the implementation thought it held_initializedand_initializing. Once those two stories shared the same storage word underdelegatecall, the initializer guard stopped being trustworthy. - The trailing
0xabacbytes of the liveproxyAdminaddress made both initialization booleans decode as truthy. That pushed theinitializermodifier down its nested-initialization branch on every call, so the contracts never reached the path that would permanently close setup. - Replayable initialization did not need to mint AUDIO directly. It only needed to rewrite the governance, staking, and delegation pointers that Audius used as vote-power truth. The fake
10T AUDIOdelegations were internal accounting artifacts, but governance still treated them as enough quorum to authorize a real treasury transfer. - This means the exploit surface and the root cause sit on different layers: the visible primitive was
initialize()replay, but the decisive bug was storage-authentication failure inside the proxy architecture. The later treasury transfer merely honored governance state that was already counterfeit.
Detection and audit pivots
- Diff proxy storage and implementation storage together, not separately. If a custom proxy keeps admin metadata outside EIP-1967-style reserved slots, ask which implementation-side guards, booleans, or packed flags now read those same bytes.
- Re-run initialization on shadow forks and already-live deployments during review. A contract that emits or accepts a meaningful
initialize()path after launch should be treated as an authorization incident until proven otherwise. - Treat
initializer presentas an incomplete audit answer. The sharper question iswhat exact live slot makes this modifier think setup already happened, and who else writes that slot? - Follow governance vote power back to its real source of truth. If staking or delegation storage can be rewritten through replayed setup, governance capture is the expected downstream symptom even when token balances and total supply never changed.
- Test whether governance-controlled treasury actions remain safe after internal vote-weight corruption. Audius is a reminder that balance-sheet loss may arrive one step after the original bug, through an otherwise ordinary proposal-execution path.
Failure-prevention checklist
- Keep proxy metadata and implementation auth state in disjoint namespaces. If a contract uses upgradeable proxies, admin slots, implementation slots, initializer flags, and ownership state should all live in layouts that cannot alias under
delegatecall; in practice that means standard reserved slots or an equally explicit equivalent. - Treat initializer guards as part of authorization, not setup ergonomics. A protocol should prove on live deployed instances that
initialize()cannot succeed again, including after proxy upgrades, storage-layout edits, or emergency hotfixes. - Make replayed initialization fail closed before any downstream governance or treasury path can matter. If governance, staking, or delegation pointers are writable during setup, then setup is already treasury authority.
- Bind governance vote power to a storage story that cannot be rewritten by configuration-only flows. Internal delegation or staking state should not become treasury-moving quorum if the same contracts still expose replayable bootstrap paths.
- Exercise recovery and emergency-upgrade tooling against the same storage assumptions the production fleet uses. Audius shows that custom proxy deviations are not harmless implementation details; they are part of the live auth boundary.
- Monitor for impossible post-launch setup activity: repeated
initialize()calls, governance-address changes inside staking modules, or sudden vote-weight jumps unsupported by real token movement. - Prefer bounded-loss treasury design even after governance auth is compromised. Proposal execution that can move the full treasury in one step turns a configuration bug into immediate balance-sheet loss.
Audit prompts
- Which exact storage word tells this deployed proxy-backed system that initialization is already done, and who else can write or reinterpret that word?
- If a custom proxy deviates from EIP-1967 or another reserved-slot scheme, what proof shows that no implementation-side guard, boolean packing, or inherited base contract reads those same bytes differently?
- Can
initialize()or any setup-equivalent selector still mutate governance pointers, guardian roles, vote accounting, or staking/delegation relationships on a live funded deployment? - Does the protocol’s notion of governance power come from balances and checkpoints that are economically anchored, or from internal delegation state that can be rewritten through setup or config paths?
- If governance were already counterfeit, what concrete control would stop proposal execution from immediately emptying the treasury: caps, delay, veto, staged release, or nothing?
- Have shadow-fork tests actually replayed initialization on production addresses and verified that repeated calls fail for the right storage reason rather than by accident?
- Which emergency upgrade, migration, or recovery flows could make a live proxy appear fresh again by changing layout, inheritance order, or initializer state assumptions?
- What telemetry would tell operators that a storage-collision bug had already become governance capture before the treasury transfer landed?
Lessons that should already be standard
- A present initializer modifier is not meaningful by itself. The live security question is whether the modifier authenticates the right storage domain on the real deployed proxy.
- Custom proxy layout is governance and treasury security, not just upgradeability plumbing. One misplaced admin slot can silently redefine who still has setup authority over the whole protocol.
- Governance capture can begin as a storage bug long before it looks like a voting bug. Once internal delegation truth is attacker-authored, proposal execution is only the cash-out step.
- Internal vote weight that is not tightly tied to economically anchored balances should be treated as a dangerous derived surface, especially when bootstrap or config paths still write it.
- Incident response that must exploit the same bug to regain control is evidence that the protocol left too little graceful containment between
setup auth failedandtreasury can move now. - Full-treasury proposal execution is an avoidable amplifier. Even if governance is compromised, bounded release and staged treasury controls can keep one auth failure from becoming total loss in one transaction.
Why this incident matters
- Audius is a sharp example of how a protocol can have a correct-looking
initializerguard and still be completely broken because proxy metadata collides with implementation state. - It shows how storage-layout errors frequently surface first as governance or admin compromise, with the actual asset movement happening only after control has already been lost.
- It also preserves a useful accounting lesson: governance systems may trust internal delegation state long before anyone notices that the apparent quorum was never backed by real token ownership.
- In corpus terms, it broadens the initializer-takeover family beyond naked
init()bugs like Parity into upgradeable-proxy guard bypasses where the one-time flag exists but cannot be trusted.
Internal linkages
- Core recurring pattern: storage-layout-collision.
- Exploit-surface manifestation: unprotected-initializer-reinitialization.
- Adjacent pattern: governance-capture.
- Strong corpus cousin: pike-finance-spoke-proxy-pause-upgrade-storage-layout-collision-reinitialization-drain-2024-04-30 — useful when the sharper question is not
custom proxy metadata already collided with initializer state at launch, butan emergency upgrade drifted storage enough to make a live proxy look fresh again.
Control surface
-
The decisive leverage sat in custom proxy admin-slot design,
delegatecallstorage aliasing, replayable initialization writes, governance vote accounting, and the treasury’s willingness to honor governance once quorum state was corrupted. -
Audius did not fail because a treasury transfer lacked access control. It failed because initialization, upgradeability, and governance accounting were all rooted in a storage layout that the proxy and implementation disagreed about.
-
The memorable control-plane boundary is that
who can initializeandwhat counts as governance voting truthwere both only as trustworthy as slot0. Once slot meaning drifted, proposal execution became a treasury-drain confirmation step rather than a defense. -
Sources:
- https://blog.audius.co/posts/audius-governance-takeover-post-mortem-7-23-22
- https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies#unstructured-storage-proxies
- https://blog.chainlight.io/en-audius-variant-scanner-scanning-storage-collisions-between-ethereum-contracts-4d2d64b77566
- https://rekt.news/audius-rekt
- https://etherscan.io/tx/0x3bbb15f9852c389e8d77399fe88b49b042d0f22aad4a33c979fbabc60a34b24f
- https://etherscan.io/tx/0xfefd829e246002a8fd061eede7501bccb6e244a9aacea0ebceaecef5d877a984
- https://etherscan.io/tx/0x4227bca8ed4b8915c7eec0e14ad3748a88c4371d4176e716e8007249b9980dc9
-
Last reviewed: 2026-08-03 UTC