Calculated from recorded token losses using historical USD prices at the incident time.
0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3fEthereum0x277697fa7c134a7fcc2aaaf812bdf1fd8b68b818Ethereum0x5c7150718038d67cf26985a9ef2ea1ef60d8f8f7EthereumIn Ethereum block 24664323, the adversary EOA 0xbdcdc1d072dfd2a1401e88959c31071fc585ce8e first deployed helper contract 0x8a9be1b19895798287a5a9b64db3d133e0297cda in tx 0x6ef464ee403b3520c9701e01721b70f50a07cc70bec4f7f15e49b2e3bba770c8, then used that helper in tx 0xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe5 to flash-borrow 1.5 WETH from Balancer Vault 0xba12222222228d8ba445958a75a0704d566bf2c8, unwrap it to ETH, and repeatedly run mint -> sync -> burn against the legacy JAKE meToken hub at 0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f.
The drain was possible because public selector 0xcfd26d5d rewrote poolBalance to the contract's full ETH balance instead of address(this).balance - lockedBalance. The subsequent burn(uint256) path trusted that corrupted reserve when calling Bancor calculateSaleReturn(uint256,uint256,uint32,uint256), so freshly minted JAKE could be burned against creator-locked ETH. The hub lost 286184417604904875 wei, and the attacker EOA ended the two-transaction sequence with 0.278038077590478003 ETH of net profit after gas.
The affected protocol component is the legacy JAKE meToken hub at 0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f, paired with JAKE meToken 0x277697fa7c134a7fcc2aaaf812bdf1fd8b68b818. The broader meTokens Hub Proxy at 0x5c7150718038d67cf26985a9ef2ea1ef60d8f8f7 is verified, but the legacy hub and JAKE token contracts themselves are not verified on Etherscan, so the analysis relies on runtime disassembly, storage inspection, and the seed trace.
The hub keeps two ETH accounting buckets. poolBalance is the Bancor-style priced reserve used for mint and burn pricing. lockedBalance is creator-locked ETH that is not supposed to be sold by ordinary buyers. Before the exploit, direct RPC reads at block 24664322 show:
getBalance() = 2741760555319110634
poolBalance() = 1919478574233703186
lockedBalance() = 822281981085407448
slot1 = 1919478574233703186
slot2 = 822281981085407448
Those values satisfy the intended invariant getBalance() = poolBalance() + lockedBalance(). The exploit breaks that invariant transiently during the public sync call and then harvests the mispricing before the burn path restores accounting.
This is an ATTACK-class ACT exploit against reserve accounting in the legacy JAKE hub. The hub exposes a public payable selector 0xcfd26d5d that writes slot 1 directly from ADDRESS.BALANCE, which means any caller can reclassify all ETH held by the contract, including creator-locked funds, as priced reserve. The normal mint path does not have this flaw: after calling Bancor calculatePurchaseReturn, it restores slot 1 to address(this).balance - lockedBalance. The burn path is vulnerable because it calls Bancor calculateSaleReturn using the tainted poolBalance before the function later recomputes storage. As a result, a buyer can mint JAKE, invoke the public sync, and immediately burn at a sale price backed by both the priced reserve and the locked creator reserve. Repeating that loop drains the priced reserve to near zero while pushing the withheld portion into lockedBalance, leaving the attacker with extracted ETH and the victim hub with materially less native balance.
The invariant and breakpoint are explicit:
Invariant: poolBalance must equal address(this).balance - lockedBalance.
Breakpoint: selector 0xcfd26d5d writes ADDRESS; BALANCE; SSTORE(slot1).
Victim runtime disassembly from direct bytecode inspection confirms the relevant control flow:
00000357: PUSH3 0x00036a
0000035c: ADDRESS
0000035d: BALANCE
0000036b: PUSH1 0x01
0000036d: SSTORE
...
00000c29: PUSH1 0x40
00000c2d: PUSH32 0x49f9b0f700000000000000000000000000000000000000000000000000000000
...
00000cb3: STATICCALL
...
00000ea0: SLOAD
00000ea6: ADDRESS
00000ea7: BALANCE
00000eb5: PUSH1 0x01
00000eb7: SSTORE
...
000016ca: JUMPDEST
000016cb: ADDRESS
000016cc: BALANCE
000016cd: PUSH1 0x01
000016cf: SSTORE
0x29a00e7c is the purchase-return selector, 0x49f9b0f7 is the sale-return selector, and 0x42966c68 is burn(uint256).
The permissionless exploit conditions were all present at block 24664322: the legacy hub exposed public selector 0xcfd26d5d, the contract held non-zero lockedBalance, and Balancer Vault exposed enough WETH liquidity for a permissionless flash loan. The attacker did not need owner privileges, private keys, or any non-public side channel. They only needed temporary capital plus access to the public mint, sync, and burn paths.
The core accounting failure is that the contract treats poolBalance as a mutable stored value instead of deriving it canonically from the contract's ETH balance minus locked funds. The mint path is internally consistent: it prices a purchase against the current reserve, mints JAKE, then recomputes slot 1 as address(this).balance - lockedBalance. The sync function breaks that safety property by storing the raw contract ETH balance into slot 1. That single write makes creator-locked ETH appear sellable.
The burn path then consumes the corrupted state. It calls calculateSaleReturn(uint256,uint256,uint32,uint256) using slot 1 as the reserve input, computes a sale quote that is too large, pays ETH out, updates lockedBalance, and only afterwards restores poolBalance. That ordering is fatal because the attacker can force the pricing step to happen while the reserve is overstated.
The seed execution trace shows exactly that sequence inside the Balancer flash-loan callback: ETH is sent into the JAKE hub fallback to mint, selector 0xcfd26d5d is called with zero value, and burn(uint256) follows immediately after. The helper repeats the same loop until the priced reserve is nearly exhausted. The attacker helper's deployed runtime also matches this strategy: its selectors include 0xdba4ce11((uint256,uint256)[]) and 0xf04f2707(address[],uint256[],uint256[],bytes), which line up with the tuple-driven attack entrypoint and Balancer callback observed on-chain.
The trace-level effect matches the accounting thesis. Before the exploit, the hub held 1.919478574233703186 ETH in poolBalance and 0.822281981085407448 ETH in lockedBalance. After the exploit tx settled in block 24664323, direct reads show:
getBalance() = 2455576137714205759
poolBalance() = 484739023893475
lockedBalance() = 2455091398690312284
slot1 = 484739023893475
slot2 = 2455091398690312284
The final state still satisfies getBalance() = poolBalance() + lockedBalance(), which is why the bug is not a permanent invariant break after settlement. The exploit instead abuses a transient invariant violation between sync and the pricing step in burn.
The adversary strategy is a two-transaction, single-block ACT sequence.
Transaction 1, 0x6ef464ee403b3520c9701e01721b70f50a07cc70bec4f7f15e49b2e3bba770c8, deploys helper contract 0x8a9be1b19895798287a5a9b64db3d133e0297cda. The deployment cost 121635152781068 wei in gas.
Transaction 2, 0xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe5, calls that helper. The helper borrows 1.5 WETH from Balancer, unwraps it, and uses the borrowed ETH as working capital for repeated mint/sync/burn cycles on the legacy hub. The exploit transaction cost 567681317633582 wei in gas.
The seed trace identifies the major stages:
Balancer Vault -> helper: 1.5 WETH flash loan
helper -> WETH.withdraw(1.5 WETH)
helper -> JAKE hub fallback with ETH value
helper -> JAKE hub selector 0xcfd26d5d
helper -> JAKE hub burn(uint256)
helper -> WETH.deposit(...)
helper -> Balancer Vault repayment
The adversary-related accounts are defensible and complete. 0xbdcdc1d072dfd2a1401e88959c31071fc585ce8e is the controlling EOA because it sent both transactions and realized the net native-ETH gain. 0x8a9be1b19895798287a5a9b64db3d133e0297cda is the attacker helper because it was deployed by that EOA and executed the flash-loan callback plus the exploit loop. The key victim-side addresses are the legacy JAKE hub 0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f, the JAKE meToken 0x277697fa7c134a7fcc2aaaf812bdf1fd8b68b818, and the public Balancer Vault liquidity source 0xba12222222228d8ba445958a75a0704d566bf2c8.
The measurable loss is native ETH from the legacy JAKE hub. The collector balance diff shows the hub balance falling from 2741760555319110634 wei to 2455576137714205759 wei during the exploit tx, a loss of 286184417604904875 wei.
The internal reserve mix changed even more dramatically. poolBalance collapsed from 1919478574233703186 wei to 484739023893475 wei, while lockedBalance increased from 822281981085407448 wei to 2455091398690312284 wei. That final state is consistent with buyer burns having been priced against funds that should have remained locked.
The attacker EOA started the two-transaction sequence with 0.1197 ETH at the end of block 24664322 and ended block 24664323 with 0.397738077590478003 ETH, for a net increase of 0.278038077590478003 ETH after both the deployment gas and exploit gas were paid. The exploit therefore realized a direct, positive ACT profit predicate, not just a griefing or accounting distortion.
0xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe50x6ef464ee403b3520c9701e01721b70f50a07cc70bec4f7f15e49b2e3bba770c80x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f0x277697fa7c134a7fcc2aaaf812bdf1fd8b68b8180xba12222222228d8ba445958a75a0704d566bf2c80xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe5getBalance(), poolBalance(), lockedBalance(), storage slots 1 and 2, and runtime disassembly for the legacy JAKE hubhttps://etherscan.io/tx/0xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe5https://etherscan.io/tx/0x6ef464ee403b3520c9701e01721b70f50a07cc70bec4f7f15e49b2e3bba770c8https://etherscan.io/address/0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3fhttps://etherscan.io/token/0x277697fa7c134a7fcc2aaaf812bdf1fd8b68b818