Calculated from recorded token losses using historical USD prices at the incident time.
0xfc7599cffea9de127a9f9c748ccb451a34d2f063Ethereum0xb30c120eb92c120bf11b358b4b9961e6679b6ae7EthereumProxy 0xfc7599cffea9de127a9f9c748ccb451a34d2f063 on Ethereum was drained for 479393838338750964434 wei (479.393838338750964434 ETH) in transaction 0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431 at block 19771059. The exploit was permissionless: an unprivileged EOA deployed helper contract 0x1da4bc596bfb1087f2f7999b0340fcba03c47fbd, used it to re-run the live implementation's external initialize(address,address,address,address,uint16,uint16) through the proxy, took over the proxy's privileged storage, then called upgradeToAndCall(address,bytes) to install attacker logic and transfer the full ETH balance out. The root cause was not the upgrade primitive itself; it was the still-callable initializer on the live proxy path, which rewrote privileged slots after deployment.
The victim system is an unverified ERC1967 proxy at 0xfc7599cffea9de127a9f9c748ccb451a34d2f063 backed before the exploit by implementation 0xb30c120eb92c120bf11b358b4b9961e6679b6ae7. The implementation behaved like a UUPS implementation: the trace and auditor analysis identify live upgradeToAndCall(address,bytes), upgradeTo(address), and proxiableUUID() behavior. In a safe deployment, initialization should be a one-time step, after which arbitrary callers must not be able to rewrite privileged configuration inside proxy storage. Here, the live implementation still exposed through proxy fallback delegation. The attacker helper was not a pre-existing protocol component; it was deployed by EOA in transaction shortly before the exploit.
initialize(address,address,address,address,uint16,uint16)0x19066f7431df29a0910d287c8822936bb7d89e230xdd7959cdbc0e71f88f12f25c40226326e1caf25e41943dbf4473b6071530a239The vulnerability class is an attacker-callable reinitialization on a live UUPS-style proxy. After deployment, privileged storage should remain bound to the legitimate operator and the upgrade path should only be reachable through that authorization model. Instead, the implementation left initialize(address,address,address,address,uint16,uint16) externally reachable on the proxy. Because the proxy executes implementation logic by delegatecall, invoking that initializer mutated proxy storage rather than harmless implementation-local state. The exploit trace shows slots 1 through 4 changing from protocol-controlled addresses to the attacker helper, and packed slot 0 changing to attacker-controlled packed values. Once the helper occupied the relevant control slots, the existing upgradeToAndCall(address,bytes) path became legitimately authorized under corrupted state. The attacker then upgraded the proxy to helper logic and executed a delegatecalled drain that transferred the full ETH balance out.
The exploitable pre-state was block 19771058, immediately before the exploit transaction, when the proxy still pointed to implementation 0xb30c120eb92c120bf11b358b4b9961e6679b6ae7 and still held 479393838338750964434 wei. In the exploit transaction, attacker helper 0x1da4bc596bfb1087f2f7999b0340fcba03c47fbd called the proxy and caused it to delegatecall the vulnerable implementation's initialize(...) entrypoint. The seed trace excerpt below shows the critical state transition:
Exploit trace excerpt:
0xFC7599...F063::initialize(0x1da4Bc..., 0x1da4Bc..., 0x1da4Bc..., 0x1da4Bc..., 20, 20)
0xb30c12...6ae7::initialize(...) [delegatecall]
emit Initialized(: 1)
storage changes:
@ 1: 0x...c933f7df0cf39909cec3eb4d507caabbefd45432 -> 0x...1da4bc596bfb1087f2f7999b0340fcba03c47fbd
@ 2: 0x...46adcf744d7c532d55cd899df3c7b0e64e1a587c -> 0x...1da4bc596bfb1087f2f7999b0340fcba03c47fbd
@ 3: 0x...27428dd2d3dd32a4d7f7c497eaaa23130d894911 -> 0x...1da4bc596bfb1087f2f7999b0340fcba03c47fbd
@ 4: 0x...54fd7ba87ddbdb4b8a28aee34ab8ffc4004687de -> 0x...1da4bc596bfb1087f2f7999b0340fcba03c47fbd
@ 0: 0x...bf5089e71a073bc4f5c80002001e01 -> 0x...1da4bc596bfb1087f2f7999b0340fcba03c47fbd0014001401
This establishes the invariant break: privileged proxy state that should have remained fixed after deployment was still writable by arbitrary callers through the initializer. After takeover, the same transaction invoked upgradeToAndCall(address,bytes) through the proxy. The implementation performed its proxiableUUID() compatibility check against the attacker helper, emitted Upgraded, rewrote the ERC1967 implementation slot from 0xb30c120eb92c120bf11b358b4b9961e6679b6ae7 to 0x1da4bc596bfb1087f2f7999b0340fcba03c47fbd, and delegatecalled into attacker logic. The attacker logic then transferred the proxy's entire ETH balance to the attacker EOA:
Exploit trace excerpt:
0xFC7599...F063::upgradeToAndCall(0x1da4Bc..., <drain calldata>)
0xb30c12...6ae7::upgradeToAndCall(...) [delegatecall]
0x1da4Bc...::proxiableUUID() -> ERC1967 implementation slot
emit Upgraded(param0: 0x1da4Bc...)
0x1da4Bc...::<drain>() [delegatecall]
0x19066f7431df29a0910d287c8822936bb7d89e23::fallback{value: 479393838338750964434}()
storage changes:
@ ERC1967 implementation slot: 0x...b30c120eb92c120bf11b358b4b9961e6679b6ae7 -> 0x...1da4bc596bfb1087f2f7999b0340fcba03c47fbd
The exploit conditions were straightforward and permissionless: the proxy had to delegate to the vulnerable implementation, the initializer had to remain callable, the attacker needed a contract returning the correct proxiableUUID(), and the proxy needed withdrawable ETH. No privileged key material, admin role, or private orderflow was required.
The adversary flow has two transactions and one decisive exploit path. First, EOA 0x19066f7431df29a0910d287c8822936bb7d89e23 deployed helper contract 0x1da4bc596bfb1087f2f7999b0340fcba03c47fbd in transaction 0xdd7959cdbc0e71f88f12f25c40226326e1caf25e41943dbf4473b6071530a239. Second, in transaction 0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431, that EOA called the helper, and the helper drove the exploit against the proxy. The helper first triggered initialize(...) so the proxy's slots 0 through 4 and initialization bookkeeping at slot 11 were overwritten in proxy context. With proxy control corrupted, the helper immediately invoked upgradeToAndCall(...), passing itself as the new implementation and drain logic target. The replacement logic executed as a delegatecall from proxy context, so the ETH transfer originated from the proxy balance itself. Collector metadata and balance diff tie the transaction sender and profit recipient together: the exploit transaction sender was the attacker EOA, and the balance diff shows that same EOA gaining 479390194178750964434 wei net of gas while the proxy balance fell to zero.
The measurable loss was the proxy's full ETH balance: 479393838338750964434 wei (479.393838338750964434 ETH). The balance diff for the exploit transaction shows:
Balance diff excerpt:
attacker EOA 0x19066f...9e23: 83153423393883758802 -> 562543617572634723236
delta: +479390194178750964434 wei
victim proxy 0xfc7599...F063: 479393838338750964434 -> 0
delta: -479393838338750964434 wei
The difference between gross drain and attacker net gain is explained by transaction fees, which the auditor quantified as 0.00364416 ETH. Beyond the immediate ETH loss, the proxy implementation pointer was also replaced with attacker-controlled code, meaning the proxy stayed under hostile implementation control until any later remediation.
0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce664310xdd7959cdbc0e71f88f12f25c40226326e1caf25e41943dbf4473b6071530a2390x0fcc85422e9726a5d8b2ce00291051df0167089e0d1a5e66bfac908c0769e05d0xa2c2e419a1d388141e6d2c47ddbfde112c06fa763b85f492ed57614250836a89/workspace/session/artifacts/collector/seed/1/0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431/trace.cast.log/workspace/session/artifacts/collector/seed/1/0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431/balance_diff.json/workspace/session/artifacts/collector/seed/1/0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431/metadata.json