
Date: August 25, 2026
Chain: Ethereum
Loss: 6,518,518.55 ENJ, about $176K, across the main exploit transaction and the smaller melts that followed before the contract was locked
Class: an initializer anyone could re-run, handing over the registry that decides how every item transfers
Every item minted on Enjin's original Ethereum CryptoItems contract had ENJ locked inside it, and anyone holding an item could melt it to get that ENJ back. By August 2026 the contract was a legacy one, superseded in December 2023 when Enjin moved its canonical assets to Enjin Blockchain, but the items and the ENJ backing them were still there.
At 18:41:59 UTC on August 25, one transaction pulled items out of dozens of holders' wallets without their approval, melted them, and delivered 5,231,353 ENJ, about $142K, to a single address. The attacker never needed a key, a signature or a holder's approval. Two registries behind the contract still let anyone re-run their setup function and make themselves manager, and the manager decided what code ran when an item moved. Smaller melts over the next three hours brought the total to 6,518,518.55 ENJ, about $176K at the ENJ price when the attack began.
Enjin built CryptoItems on ERC-1155, a token standard that lets one contract hold many kinds of items, fungible and non-fungible, side by side. What made Enjin's version distinctive was melting. When an item was created, ENJ was locked in a reserve to back it, and destroying the item released that ENJ to whoever held it. That design gave in-game items a floor value, and it also meant that anyone who could take an item could turn it straight into ENJ.
Transfers on the platform ran through per-item adapters. According to the DeFiHackLabs proof of concept, those adapters delegatecall into code registered in a registry, which means they borrow whatever function body the registry points to and run it as their own. Whoever managed the registry decided what a transfer actually did.
An initialize() function is the setup step a contract runs once, at deployment, to record who its owner or manager is. The standard protection is a flag that marks setup as done and rejects every later call. The DeFiHackLabs write-up found that two live registries carried an unprotected initialize(), so any unrelated caller could re-run it and then call acceptManager() to complete the handover. The PoC author is explicit that the re-initialization itself is what granted the privilege, with no signer or admin compromise involved.
Picture an apartment building whose management office will issue a master key to anyone who fills in the "new building manager" form, because the form was only ever supposed to be filled in once, on opening day. The single idea to take from this case is that an initializer is the most privileged function a contract has, and it needs the same guard as every admin function plus a lock that makes it run exactly once.
Enjin's own incident report describes the cause as a flaw in the legacy contract's authorization logic, and confirms that no wallets or private keys were compromised.
The attacker's wallet, 0x5ec1...8Ca5, sent the exploit transaction to a contract at 0x7083...D321, which created nine contracts during the call. Following the path the PoC reproduces, the attack contract re-initialized the registries, accepted the manager role, and called updateContract to register its own payload as the code item adapters delegatecall into for transfers. That payload had no owner-approval check, so it skipped the rule that a transfer must come from the holder or from an address the holder approved with setApprovalForAll.
With that code in place, the attack contract pulled items out of holders' wallets into itself and melted each one. Etherscan shows 108 ERC-1155 transfers in the transaction, most of them single items moving into 0x7083...D321 and then out to the zero address as burns. The reserve paid out ENJ for each melt, because from the contract's point of view the attack contract now held every item it was melting, and the transaction ended with 5,231,353 ENJ sent to the attacker's wallet. Defimon's alert put the number of victims at about 52 holders, and the DeFiHackLabs PoC replays the same 54 items and holders recorded in the transaction's events.
The main transaction wasn't the last one. Blockscout shows the reserve paying ENJ to the attack contract 648 times across 595 transactions between 18:41:59 and 21:46:35 UTC, and the attack contract passing that ENJ on to the attacker's wallet in four transfers: 5,231,353 ENJ at 18:41, 3,070 at 19:44, 746,479 at 20:32 and 537,616.56 at 21:46, for 6,518,518.55 ENJ in all. Enjin observed the exploit at 18:42 UTC and placed the contract in a locked state at 00:02 UTC on August 26, about five hours and twenty minutes later. The lock was a safeguard built into the contract at deployment, and after it went on, further exploit attempts failed. Enjin says the attacker converted the proceeds and moved them off Ethereum, and that it's sharing indicators with exchanges and analytics partners.
The report also shows how the loss was spread. More than 80% of the ENJ redeemed backed early proof-of-concept items created during development, the rest, about 1.19 million ENJ, backed other legacy items, and only 415 wallets lost any ENJ at all. Among affected wallets, 95% lost none, 97% lost under 1,000 ENJ and 99.6% lost under 10,000 ENJ. Enjin Blockchain, native ENJ and the legacy ENJ ERC-20 contract weren't affected, and whether the CryptoItems contract can be safely upgraded and unlocked is under review.
Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the architecture around the code, here a registry whose manager chooses the logic every item adapter delegatecalls into. From that it infers the invariants the system depends on, and this one is easy to state: only an authorized party can ever become registry manager, and no item can leave a holder's wallet unless the holder or an approved operator moved it.
A platform with thousands of items has a huge state space, and signals are how the engine decides which paths to attack first. Olympix's static analysis surfaces the signal here, an initializer with no caller check and no once-only guard, sitting in a contract whose manager role controls code that other contracts execute. That narrows the search from every path, to the feasible ones, to the short sequence that touches the manager role.
That open initializer is where BugPoCer goes to work, starting from the platform's two rules and running Counterexample-Guided Invariant Refutation against them until one gives way. The manager rule breaks in two calls, a fresh address calling initialize() and then acceptManager() on a registry it has no business controlling. Extending that path to updateContract and a transfer out of a holder's wallet the holder never approved breaks the custody rule as well. BugPoCer returns the whole sequence as a counterexample with the minimal conditions that trigger it and a generated Forge test that replays it, which is the August 25 transaction produced in development.
Olympix has flagged this class before in other codebases, where a transfer path ran without checking that the caller was the owner or an approved operator. Because the engine runs in development, at every merge and before release, a setup function that stays callable after deployment shows up as a finding long before it's sitting next to a reserve.
Enjin's CryptoItems contract outlived its role as the platform's main home, and the registries behind it kept honoring a setup step that should have closed for good after its first use. Any upgradeable or registry-driven system should treat its initializers as admin functions with a one-time lock and test that a stranger can't call them years later. To see Olympix map the privileged roles in your contracts and return a runnable counterexample for every path that reaches one, book a technical walkthrough.
Protocol: Enjin CryptoItems (legacy ERC-1155 contract on Ethereum)
Chain: Ethereum
Date: August 25, 2026, 18:41:59 UTC
Loss: 6,518,518.55 ENJ (about $176K), of which 5,231,353 ENJ came in the main exploit tx and the rest in later melts before the lock
Attacker EOA: 0x5ec1...8Ca5
Attack contract: 0x7083...D321
CryptoItems contract (victim): 0xfaaF...043C
ENJ token: 0xf629...3B9c
Exploit tx: 0xd4a3...2a7e
Block: 25,834,071
Registry A (re-initialized): 0x13fA...b157
Registry B (re-initialized): 0x268C...B27f
ENJ reserve: 0x4E64...174e
Original registry manager: 0x1952...880c
Enjin: Exploit of a Legacy Enjin NFT Contract on Ethereum
DeFiHackLabs PR #1278: EnjinCryptoItems Unprotected Registry Reinitialization PoC