
Date: September 5, 2026
Chain: BNB Chain
Loss: 71,851.02 USDT, about $72K
Class: award state machine that let a claimed reward go back to claimable
Dream Health Chain's award contract kept a simple ledger for each reward: a status of 1 meant the award was active and could be claimed, and a status of 2 meant it had already been paid. On September 5, 2026, an attacker found that a 2 could be turned back into a 1 for almost nothing, as little as 0 to 1 wei per reset according to SlowMist's alert.
That one backward step was the whole exploit. Each time the attacker reset a claimed award, claimAward() paid the same fixed reward again out of a balance shared by every award in the system, and the attacker repeated the cycle until the contract's DHC was gone and 71,851.02 USDT had landed in the attacker's wallet. It was possible because participateAward(), the function that moves an award into the active state, never checked whether the award was already finished.
Dream Health Chain ran its award system through three functions behind an upgradeable proxy. createAward() set up a reward with a fixed payout, participateAward() moved an award into the active state, and claimAward() paid the reward out and marked it claimed. The payouts came from a single DHC balance held by the proxy. The award implementation was never verified on BscScan, so these names are the ones SlowMist and the DeFiHackLabs reproduction use for the behavior they observed in the trace.
According to those analyses and the reproduced exploit indexed by crypto.training, createAward() recorded a fixed reward without locking any collateral for that specific award. Every award's promised payout was backed by the same pool of funds, so an award that could be paid twice was paid out of everyone else's share.
An award was meant to move one way: created, then active, then claimed. Claimed was supposed to be the end. claimAward() enforced the forward direction, paying only awards that were active, but nothing enforced that a claimed award stayed claimed. participateAward() didn't require the award to be in a valid starting state, so calling it on a status 2 award wrote the status back to 1, and claimAward() saw an active award with an unpaid reward like any other.
Any contract with a lifecycle needs the same thing: every state transition has to guard where it's allowed to start from, including the ones that only make sense in one direction. Think of a coat check that hands back the coat when you show a ticket, stamps the ticket as used, and then lets anyone get the stamp removed at a second window. The first window works perfectly, and the coats still walk out the door.
The whole attack fit into BSC block 120055460. DeFiHackLabs reconstructs it as four transactions from one fresh address: a deployment of the exploit contract and three calls into it. The first call took a USDT flash loan from DODO, bought DHC on PancakeSwap, registered an inviter (the award system rejected participants without one), and created an award of its own with a fixed reward of about 14,016.5 DHC. The second call participated in that award with the full pledge and claimed it once, which moved it to status 2.
The third call was the loop. It called participateAward() on the claimed award with a dust amount to set its status back to 1, called claimAward() to collect the fixed payout again, and repeated until the proxy's DHC balance could no longer cover another reward. The caller was the attacker's contract, not the proxy's owner, so nothing about the cycle needed special permission. The exploit contract then sold the drained DHC, about 542,070 tokens by SlowMist's count, into the DHC/USDT pool on PancakeSwap, repaid the flash loan, and left the attacker's address 71,851.02 USDT richer.
As of October 19, 2026, the attacker's address still held all 71,851.02 USDT, with no outgoing token transfers on the explorer. No public response from the Dream Health Chain team turned up in reporting on the incident.
Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it infers the rules the system relies on from the code and its context. In Dream Health Chain's award contract, the state is each award's status and the shared balance behind it, and the entry points are the three award functions. The engine maps every transition between statuses and uses signals to decide which paths to attack. The signal Olympix's static analysis surfaces here is a public function that writes an award's status without checking what the status was before, in a contract where that status decides whether money goes out. Olympix has flagged this class before in other codebases.
From there BugPoCer treats the award ledger as a system with a promise to keep, and Counterexample-Guided Invariant Refutation is how it tests that promise. The promise fits in one sentence: once an award has been claimed, it can never be paid again. BugPoCer reads that rule out of the way claimAward() pays and stamps awards, then walks every ordering of create, participate and claim it can reach, looking for the one where a stamped award gets paid a second time.
The counterexample BugPoCer returns is this exploit in miniature: create an award, participate, claim it, call participateAward() on the claimed award, and claim it again, with the shared balance lower by two payouts instead of one. It comes with the minimal conditions that trigger it and a generated Forge test that replays the loop. Because Olympix runs at every merge and before release, that test would exist in development, before the award contract held anyone's funds.
Dream Health Chain's award contract guarded the step into claimed and left the step out of it open. For builders, a terminal state is only terminal if every function that can write the status checks where it's starting from, and a shared payout pool turns any repeatable claim into a drain on everyone. To see how Olympix maps every transition in your contracts' state machines and hands back a runnable counterexample for the one that runs backward, book a technical walkthrough.
Protocol: Dream Health Chain (DHC award contract)
Chain: BNB Chain
Date: September 5, 2026, 05:34:48 UTC
Block: 120055460
Loss: 71,851.02 USDT
Attacker EOA: 0xD3A8...2B07
Exploit contract: 0x2269...968A
Award vault proxy (victim): 0xe2A0...2094
Award implementation (source unverified): 0x5ABB...8CE1
DHC token: 0x743F...d6E9
DHC/USDT PancakeSwap V2 pool: 0x4b92...e5f4
Exploit contract deployment tx: 0x172c...7cc9
First call (0xc0406226): 0xd65c...5416
Second call (0x9890220b): 0x0377...cc51
Third call, USDT payout to attacker (0x84054d3d): 0xb4c8...c496
SlowMist: Dream Health Chain exploit alert
DeFiHackLabs: DHC exploit reproduction (DHC_exp.sol)
SlowMist Hacked: BSC incident database (Dream Health Chain, 2026-09-05)
crypto.training: Reproduced exploits index (DHC Award vault)